Cabinee Connector
Artifact names (
unibpm-external-task-unibpm, YAML prefixunibpm, connector idunibpm) are historical technical identifiers. The product is Cabinee.
Purpose
unibpm-external-task-unibpm is a Cabinee connector for performing internal platform actions through the External Task mechanism.
The connector is intended for use in unibpm-integration-runtime and lets processes call Cabinee business functions without accessing the REST API directly from the BPMN model.
The process model operates with business actions (action), while HTTP/Feign details, URLs, and credentials remain at the runtime configuration level.
This approach follows the overall External Task architecture in Cabinee:
unibpm-apppublishes the job to the jobs topicunibpm-integration-runtimereceives it and finds a matching handler- the handler uses
unibpm-api-client unibpm-appexecutes the business logic and returns the result
Architectural principle
The unibpm connector is a business connector, not a technical one.
This means that BPMN specifies:
uni.connectoruni.actionuni.version
and business inputs/outputs
BPMN must not specify:
- HTTP method
- URL
- path
- query parameters
- credentials
Example of the correct approach:
uni.connector = unibpm
uni.action = comments.add
uni.version = v1
Example of an incorrect approach:
uni.connector = unibpm
uni.action = rest.invoke
unibpm.method = POST
unibpm.path = /api/...
Enabling the connector
unibpm:
integration:
connectors:
unibpm:
enabled: true
API client configuration
The connector handlers use unibpm-api-client.
Credentials and the URL are determined not in the handlers, but at the level of a specific unibpm-integration-runtime instance.
Example:
unibpm:
client:
url: http://unibpm-app:8099
The authorization method depends on the unibpm-api-client / FeignClient configuration.
Important:
The External Task handler must not know credentials.
This is a fundamental architectural agreement.
Common External Task contract
Base extension properties
uni.connector = unibpm
uni.action = <business-action>
uni.version = v1
Input parameters
unibpm.in.<name> = <value or expression>
Output parameters
unibpm.out.<name> = <processVariableName>
Process variable substitution
Expressions of the following form are supported:
unibpm.in.ticketId = ${ticketId}
Value resolution is performed through ExtensionPropertyResolver.
Result projection (ticket.select)
For operations that return a ticket object (ticket.get, ticket.duplicate, ticket.move, ticket.merge), the following property is supported:
ticket.select = id,number,name
The property works as a whitelist projection of Cabinee API response fields.
The following are supported:
- simple fields (
id,number,name,priority,impact,dueDate) - nested fields via dot notation (
owner.id,owner.name,type.id,type.name,state.id,state.name)
If ticket.select is not set, only the following fields are returned by default:
id,number,name
This is done so that the full ticket JSON is not stored in process variables, which can be too large for Camunda (for example, because of body, nested objects, and lists).
The result is returned in a single process variable:
unibpm.out.result
Example:
uni.connector = unibpm
uni.action = ticket.get
uni.version = v1
unibpm.in.ticketId = ${ticketId}
ticket.select = id,number,name,owner.id,owner.name,type.id,type.name,priority
Result:
{
"id": "5f758930-f1bb-48be-b687-d2518a94466a",
"number": 6567,
"name": "Bugs. Errors when downloading a PDF file in the file picker",
"owner": {
"id": "v.ivanova@acme.com",
"name": "Victoria Ivanova"
},
"type": {
"id": "72a23e56-85c6-410b-865c-dbd9b04a6aac",
"name": "Issue"
},
"priority": "VERY_HIGH"
}
If ticket.select specifies a field that does not exist, the result returns null for that field.
Base abstract handler
All connector handlers inherit from AbstractUnibpmHandler.
Responsibilities of AbstractUnibpmHandler
- fixes
connector = "unibpm" - fixes
version = "v1" - provides helper methods for reading extension properties
- centralizes the use of
ExtensionPropertyResolver
Implemented operations
At this stage, the connector implements the following business operations:
comments.addapprovals.results.getapprovals.aggregatemessage.sendticket.assign-ownerticket.assign-responsibleticket.change-bodyticket.change-due-dateticket.change-impactticket.change-nameticket.change-priorityticket.change-typeticket.getticket.duplicateticket.moveticket.merge
1. Operation comments.add
Purpose
Adds an internal comment to a Cabinee ticket.
The comment is created as INTERNAL.
Action
uni.action = comments.add
Inputs
Required
unibpm.in.ticketId
unibpm.in.body
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.body— comment text/HTML
Outputs
Optional
unibpm.out.commentId
unibpm.out.createdAt
unibpm.out.commentId— process variable name where the ID of the created comment is storedunibpm.out.createdAt— process variable name where the comment creation time is stored
Example
uni.connector = unibpm
uni.action = comments.add
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.body = <p>Comment added by the process</p>
unibpm.out.commentId = createdCommentId
unibpm.out.createdAt = createdCommentCreatedAt
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
If output-mapping properties are set, the following are written to the process:
- comment ID
- comment creation time
Notes
- the comment is always created as
CommentType.INTERNAL - credentials are determined by the Feign client configuration
- REST API details are not used in BPMN
2. Operation approvals.results.get
Purpose
Retrieves the list of stored approval results (ApprovalDecisionDto) for a ticket.
Used when the process needs the raw list of approver decisions.
The operation returns all stored approval results for the ticket without additional filtering by approval attempt or stage.
Action
uni.action = approvals.results.get
Inputs
Required
unibpm.in.ticketId
Outputs
Optional
unibpm.out.results
unibpm.out.count
unibpm.out.results— process variable name for the list ofApprovalDecisionDtounibpm.out.count— process variable name for the number of decisions
Example
uni.connector = unibpm
uni.action = approvals.results.get
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.out.results = approvalResults
unibpm.out.count = approvalResultsCount
Result
The following may be written to the process:
approvalResults— list ofApprovalDecisionDtoobjectsapprovalResultsCount— number of decisions
If the process needs to filter results by a specific approval attempt and stage, use the approvals.aggregate operation.
3. Operation approvals.aggregate
Purpose
Retrieves approval results for a ticket and immediately returns them in an aggregated business form.
This is the recommended option for a process that needs to make a decision immediately:
- approved
- return for rework
Action
uni.action = approvals.aggregate
Aggregation business logic
The handler retrieves approval results from Cabinee by:
- ticket
- approval attempt
- approval stage
Then the aggregation rule explicitly specified in the extension properties is applied.
The current version supports the following rule:
ANY_REJECT_REWORK
Meaning of the rule:
- if there is at least one result equal to
notApprovedValue, the outcome isREVISION_REQUIRED - if there are no
notApprovedValueresults, the outcome isAPPROVED
Additionally, the handler:
- counts the number of approvals
- counts the number of rejections
- collects comments from those who rejected
- can return the raw list of decisions
Inputs
Required
unibpm.in.ticketId
unibpm.in.approval.attempt
unibpm.in.approval.stage
unibpm.in.approval.rule
Optional
unibpm.in.approval.approvedValue
unibpm.in.approval.notApprovedValue
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.approval.attempt— approval attempt numberunibpm.in.approval.stage— approval stage nameunibpm.in.approval.rule— aggregation rule for approval resultsunibpm.in.approval.approvedValue— value treated as a positive decision (default:APPROVED)unibpm.in.approval.notApprovedValue— value treated as a negative decision (default:NOT_APPROVED)
Outputs
Optional
unibpm.out.result
unibpm.out.approveCount
unibpm.out.rejectCount
unibpm.out.comments
unibpm.out.rawResults
Values
unibpm.out.result— final business decisionunibpm.out.approveCount— number of decisions equal toapprovedValueunibpm.out.rejectCount— number of decisions equal tonotApprovedValueunibpm.out.comments— list of comments from approvers who rejectedunibpm.out.rawResults— raw list ofApprovalDecisionDto
Example
uni.connector = unibpm
uni.action = approvals.aggregate
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.approval.attempt = ${approvalAttempt}
unibpm.in.approval.stage = ${approvalStage}
unibpm.in.approval.rule = ANY_REJECT_REWORK
unibpm.in.approval.approvedValue = APPROVED
unibpm.in.approval.notApprovedValue = NOT_APPROVED
unibpm.out.result = approvalResult
unibpm.out.approveCount = approvalApproveCount
unibpm.out.rejectCount = approvalRejectCount
unibpm.out.comments = approvalRejectComments
unibpm.out.rawResults = approvalResults
Example result
{
"approvalResult": "REVISION_REQUIRED",
"approvalApproveCount": 2,
"approvalRejectCount": 1,
"approvalRejectComments": [
"ne ok"
]
}
Recommended usage in BPMN
Gateway
${approvalResult == "APPROVED"}
and
${approvalResult == "REVISION_REQUIRED"}
Using comments
approvalRejectComments can be used:
- in a rework task
- in a ticket comment
- in notifications
- in the UI
Recommended BPMN pattern
For an approval case, use approvals.aggregate rather than raw ApprovalDecisionDto objects when the process needs to make a decision.
Recommended variant
Start
->
ServiceTask approvals.aggregate
->
ExclusiveGateway
├─ APPROVED
└─ REVISION_REQUIRED
Why this is better
- BPMN remains business-oriented
- the gateway uses a simple variable
- there is no need to write complex FEEL/EL expressions over a DTO array
- the model is easier to maintain
Error handling
On technical errors, handlers throw ExternalTaskFailureException.
Typical causes:
- a required extension property is missing
${variable}could not be resolvedticketIdhas an invalid UUID formatunibpm-appis unavailable- the API returned an error
- an unsupported aggregation rule is set in
unibpm.in.approval.rule
Typical retry timeout:
protected static final long DEFAULT_RETRY_DURATION_MS = 30_000L;
4. Operation message.send
Purpose
Creates an outbound message in Cabinee through SenderMessageApi.
The operation is used to place a message in the Cabinee outbound queue. Actual delivery is performed later by the platform’s internal mechanisms.
Action
uni.action = message.send
Business logic
The handler:
- creates a
SenderMessage - sets
direction = OUTBOX - sets
completed = false - sets
retries = 3 - calls
SenderMessageApiClient.createSenderMessage(...) - returns the created message identifier and its processing flag to the process
Inputs
Required
unibpm.in.sender
unibpm.in.recipients
unibpm.in.subject
Optional
unibpm.in.copy
unibpm.in.body
unibpm.in.channelName
unibpm.in.inReplyTo
unibpm.in.attachments
Values
unibpm.in.sender— message senderunibpm.in.recipients— comma-separated list of recipientsunibpm.in.copy— comma-separated list of CC recipientsunibpm.in.subject— message subjectunibpm.in.body— message bodyunibpm.in.channelName— delivery channel nameunibpm.in.inReplyTo— parent message identifier / value for a reply scenariounibpm.in.attachments— comma-separated list of attachment UUIDs
Outputs
The handler always returns the following process variables:
messageId— UUID of the created messagemessageCompleted— whether the message has already been processed by the delivery system.
At the time of creation, the value is usually false,
because delivery is performed asynchronously by Cabinee internal mechanisms.
Example
uni.connector = unibpm
uni.action = message.send
uni.version = v1
unibpm.in.sender = info@reunico.com
unibpm.in.recipients = employee@company.com
unibpm.in.copy = hr@company.com
unibpm.in.subject = Vacation request approved
unibpm.in.body = Good afternoon!
Your vacation request has been approved. The required documents are currently being prepared. After they are ready, you will receive a notification to sign them.
unibpm.in.channelName = EMAIL
Example with variable substitution
uni.connector = unibpm
uni.action = message.send
uni.version = v1
unibpm.in.sender = info@reunico.com
unibpm.in.recipients = ${employeeEmail}
unibpm.in.subject = Vacation request not approved
unibpm.in.body = Good afternoon!
Your vacation request was not approved.
Comments on the request: ${approvalRejectComments}
unibpm.in.channelName = EMAIL
unibpm.in.attachments = ${attachmentIds}
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status and the following process variables:
messageIdmessageCompleted
Notes
- the action does not send the message directly to an external channel; it creates a
SenderMessagerecord in Cabinee unibpm.in.recipients,unibpm.in.copy, andunibpm.in.attachmentsare passed as CSV stringsunibpm.in.bodycan be multilineunibpm.in.channelNameis optional and can be used for routing by delivery channels- outbound retries are automatically set to
3
Recommended usage in BPMN
Use the action for user and system process notifications:
- approval notification
- return-for-rework notification
- service messages to process participants
Example
Start
->
ServiceTask message.send
->
End
5. Operation ticket.assign-owner
Purpose
Assigns the owner of a Cabinee ticket or clears the owner.
Action
uni.action = ticket.assign-owner
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.ownerId
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.ownerId— identifier of the user to assign as owner
If unibpm.in.ownerId is not set, is null, or contains only whitespace, the owner is cleared.
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.assign-owner
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.ownerId = ${employeeKey}
6. Operation ticket.assign-responsible
Purpose
Assigns the responsible person for a Cabinee ticket or clears the responsible person.
Action
uni.action = ticket.assign-responsible
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.responsibleId
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.responsibleId— identifier of the user to assign as responsible
If unibpm.in.responsibleId is not set, is null, or contains only whitespace, the responsible person is cleared.
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.assign-responsible
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.responsibleId = ${employeeKey}
7. Operation ticket.change-body
Purpose
Changes the body of a Cabinee ticket.
Action
uni.action = ticket.change-body
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.body
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.body— new ticket body
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.change-body
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.body = <p>Updated ticket description</p>
8. Operation ticket.change-due-date
Purpose
Changes the due date of a Cabinee ticket.
Action
uni.action = ticket.change-due-date
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.dueDate
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.dueDate— new due date inyyyy-MM-ddformat
If unibpm.in.dueDate is not set, is null, or contains only whitespace, the due date is cleared.
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.change-due-date
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.dueDate = 2026-05-01
9. Operation ticket.change-impact
Purpose
Changes the impact of a Cabinee ticket.
Action
uni.action = ticket.change-impact
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.impact
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.impact— newImpactvalue
Allowed Impact values:
VERY_LOWLOWMEDIUMHIGHVERY_HIGH
If unibpm.in.impact is not set, is null, or contains only whitespace, the Impact value is cleared.
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.change-impact
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.impact = HIGH
10. Operation ticket.change-name
Purpose
Changes the name of a Cabinee ticket.
Action
uni.action = ticket.change-name
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.name
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.name— new ticket name
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.change-name
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.name = Updated ticket name
11. Operation ticket.change-priority
Purpose
Changes the priority of a Cabinee ticket.
Action
uni.action = ticket.change-priority
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.priority
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.priority— newTicketPriorityvalue
Allowed TicketPriority values:
VERY_LOW— very low: cosmetic,nice-to-haveLOW— low: minor issue / questionMEDIUM— medium: ordinary incidents / requests, workarounds existHIGH— high: user work is blocked, the issue occurs frequentlyVERY_HIGH— very high: critical blocker, mass impact, business processes are stopped
If unibpm.in.priority is not set, is null, or contains only whitespace, the priority is cleared.
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.change-priority
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.priority = VERY_HIGH
12. Operation ticket.change-type
Purpose
Changes the type of a Cabinee ticket.
Action
uni.action = ticket.change-type
Inputs
Required
unibpm.in.ticketId
Optional
unibpm.in.typeId
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.typeId— UUID of the new ticket type (TicketType)
If unibpm.in.typeId is not set, is null, or contains only whitespace, the type is cleared.
Result
On success, the handler returns ExternalTaskResult with the COMPLETED status.
Example
uni.connector = unibpm
uni.action = ticket.change-type
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.typeId = ${ticketTypeId}
13. Operation ticket.get
Purpose
Retrieves a Cabinee ticket and returns its projection to the process.
Action
uni.action = ticket.get
Inputs
Required
unibpm.in.ticketId
Optional
ticket.select
Values
unibpm.in.ticketId— ticket UUIDticket.select— comma-separated whitelist of result fields
If ticket.select is not set, only the id, number, and name fields are returned by default.
Outputs
The handler returns a single process variable:
unibpm.out.result
Example
uni.connector = unibpm
uni.action = ticket.get
uni.version = v1
unibpm.in.ticketId = ${ticketId}
ticket.select = id,number,name,owner.id,owner.name,state.id,state.name
14. Operation ticket.duplicate
Purpose
Creates a duplicate of a Cabinee ticket and returns a projection of the created ticket to the process.
Action
uni.action = ticket.duplicate
Inputs
Required
unibpm.in.ticketId
Optional
ticket.select
Values
unibpm.in.ticketId— UUID of the source ticketticket.select— comma-separated whitelist of result fields
If ticket.select is not set, only the id, number, and name fields are returned by default.
Outputs
The handler returns a single process variable:
unibpm.out.result
Example
uni.connector = unibpm
uni.action = ticket.duplicate
uni.version = v1
unibpm.in.ticketId = ${ticketId}
ticket.select = id,number,name,workflow.id,workflow.name,type.id,type.name
15. Operation ticket.move
Purpose
Moves a Cabinee ticket to another space (workflow) and, if needed, changes its type.
Action
uni.action = ticket.move
Inputs
Required
unibpm.in.ticketId
unibpm.in.workflowId
Optional
unibpm.in.ticketTypeId
ticket.select
Values
unibpm.in.ticketId— ticket UUIDunibpm.in.workflowId— UUID of the target workflowunibpm.in.ticketTypeId— UUID of the target ticket typeticket.select— comma-separated whitelist of result fields
If unibpm.in.ticketTypeId is not set, is null, or contains only whitespace, the ticket type is not changed during the move.
If ticket.select is not set, only the id, number, and name fields are returned by default.
Outputs
The handler returns a single process variable:
unibpm.out.result
Example
uni.connector = unibpm
uni.action = ticket.move
uni.version = v1
unibpm.in.ticketId = ${ticketId}
unibpm.in.workflowId = ${targetWorkflowId}
unibpm.in.ticketTypeId = ${targetTicketTypeId}
ticket.select = id,number,name,workflow.id,workflow.name,type.id,type.name
16. Operation ticket.merge
Purpose
Merges two Cabinee tickets and returns a projection of the target ticket after the merge to the process.
Action
uni.action = ticket.merge
Inputs
Required
unibpm.in.sourceTicketId
unibpm.in.targetTicketId
Optional
ticket.select
Values
unibpm.in.sourceTicketId— UUID of the source ticket that will be mergedunibpm.in.targetTicketId— UUID of the target ticket into which the merge is performedticket.select— comma-separated whitelist of result fields
If ticket.select is not set, only the id, number, and name fields are returned by default.
Outputs
The handler returns a single process variable:
unibpm.out.result
Example
uni.connector = unibpm
uni.action = ticket.merge
uni.version = v1
unibpm.in.sourceTicketId = ${sourceTicketId}
unibpm.in.targetTicketId = ${targetTicketId}
ticket.select = id,number,name,state.id,state.name,workflow.id,workflow.name
Further development plan
Potential next business actions:
tickets.searchcomments.listattachments.signattachments.verifyworkflows.startpermissions.check
Summary
unibpm-external-task-unibpm is a system business connector for Cabinee that calls internal platform capabilities through External Task.
Its key properties:
- a business contract
- no transport details in BPMN
- use of
unibpm-api-client - no knowledge of credentials inside handlers
- support for ticket operations for working with Cabinee tickets
- straightforward extension with new
actionvalues