Book a Demo

Cabinee Connector

Artifact names (unibpm-external-task-unibpm, YAML prefix unibpm, connector id unibpm) 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-app publishes the job to the jobs topic
  • unibpm-integration-runtime receives it and finds a matching handler
  • the handler uses unibpm-api-client
  • unibpm-app executes 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.connector
  • uni.action
  • uni.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.add
  • approvals.results.get
  • approvals.aggregate
  • message.send
  • ticket.assign-owner
  • ticket.assign-responsible
  • ticket.change-body
  • ticket.change-due-date
  • ticket.change-impact
  • ticket.change-name
  • ticket.change-priority
  • ticket.change-type
  • ticket.get
  • ticket.duplicate
  • ticket.move
  • ticket.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 UUID
  • unibpm.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 stored
  • unibpm.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 of ApprovalDecisionDto
  • unibpm.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 of ApprovalDecisionDto objects
  • approvalResultsCount — 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 is REVISION_REQUIRED
  • if there are no notApprovedValue results, the outcome is APPROVED

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 UUID
  • unibpm.in.approval.attempt — approval attempt number
  • unibpm.in.approval.stage — approval stage name
  • unibpm.in.approval.rule — aggregation rule for approval results
  • unibpm.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 decision
  • unibpm.out.approveCount — number of decisions equal to approvedValue
  • unibpm.out.rejectCount — number of decisions equal to notApprovedValue
  • unibpm.out.comments — list of comments from approvers who rejected
  • unibpm.out.rawResults — raw list of ApprovalDecisionDto

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"
  ]
}

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

For an approval case, use approvals.aggregate rather than raw ApprovalDecisionDto objects when the process needs to make a decision.

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 resolved
  • ticketId has an invalid UUID format
  • unibpm-app is 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 sender
  • unibpm.in.recipients — comma-separated list of recipients
  • unibpm.in.copy — comma-separated list of CC recipients
  • unibpm.in.subject — message subject
  • unibpm.in.body — message body
  • unibpm.in.channelName — delivery channel name
  • unibpm.in.inReplyTo — parent message identifier / value for a reply scenario
  • unibpm.in.attachments — comma-separated list of attachment UUIDs

Outputs

The handler always returns the following process variables:

  • messageId — UUID of the created message
  • messageCompleted — 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:

  • messageId
  • messageCompleted

Notes

  • the action does not send the message directly to an external channel; it creates a SenderMessage record in Cabinee
  • unibpm.in.recipients, unibpm.in.copy, and unibpm.in.attachments are passed as CSV strings
  • unibpm.in.body can be multiline
  • unibpm.in.channelName is optional and can be used for routing by delivery channels
  • outbound retries are automatically set to 3

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 UUID
  • unibpm.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 UUID
  • unibpm.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 UUID
  • unibpm.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 UUID
  • unibpm.in.dueDate — new due date in yyyy-MM-dd format

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 UUID
  • unibpm.in.impact — new Impact value

Allowed Impact values:

  • VERY_LOW
  • LOW
  • MEDIUM
  • HIGH
  • VERY_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 UUID
  • unibpm.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 UUID
  • unibpm.in.priority — new TicketPriority value

Allowed TicketPriority values:

  • VERY_LOW — very low: cosmetic, nice-to-have
  • LOW — low: minor issue / question
  • MEDIUM — medium: ordinary incidents / requests, workarounds exist
  • HIGH — high: user work is blocked, the issue occurs frequently
  • VERY_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 UUID
  • unibpm.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 UUID
  • ticket.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 ticket
  • ticket.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 UUID
  • unibpm.in.workflowId — UUID of the target workflow
  • unibpm.in.ticketTypeId — UUID of the target ticket type
  • ticket.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 merged
  • unibpm.in.targetTicketId — UUID of the target ticket into which the merge is performed
  • ticket.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.search
  • comments.list
  • attachments.sign
  • attachments.verify
  • workflows.start
  • permissions.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 action values