BPMN Authoring Reference
Process Orchestration is currently available as a Non-Production Preview (NPP) feature, available to a limited number of customers. You should not process production data with NPP features. To request access, contact your Mambu Customer Success Manager. For more information, see Mambu Release Cycle — Feature Release Status.
This page is the complete authoring reference for Process Orchestration BPMN files. For a step-by-step introduction, see Getting Started.
BPMN file structure
Every process definition must declare the Mambu BPMN extension namespace and set the correct targetNamespace:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:mbu="http://mambu.com/bpmn"
targetNamespace="process-orchestration">
<process id="myProcess" name="My Process" isExecutable="true">
<!-- process elements go here -->
</process>
</definitions>
| Attribute | Required value |
|---|---|
xmlns | http://www.omg.org/spec/BPMN/20100524/MODEL |
xmlns:mbu | http://mambu.com/bpmn |
targetNamespace | process-orchestration |
The process/@id is the key used when starting an instance — see Process Instances API for the endpoint reference.
Destinations
A destination is a named, authenticated HTTP connection registered via the Destinations API. BPMN tasks reference destinations by their externalId; credentials are managed server-side and never appear in the BPMN file itself.
Authentication types
authType | secureProperties fields | How it works |
|---|---|---|
BASIC | username, password | HTTP Basic Authorization header |
API_KEY | apiKey, headerName | Named header with the API key value. headerName defaults to api-key if omitted. |
Destination lifecycle
DRAFT → PENDING_APPROVAL → APPROVED → CURRENT
└→ REJECTED
Only destinations in CURRENT status can be used by running process instances. Updating a destination (e.g., rotating a credential) creates a new version and requires re-approval.
Deleting a destination that is referenced by a running process instance causes those steps to fail. Check active instances before deleting.
Service tasks
Service tasks perform work within the process. Set mbu:type on a <serviceTask> to select the task type.
call-destination — HTTP request
Makes an authenticated HTTP request to an external system via a registered destination.
<serviceTask id="myTask" name="Call External API" mbu:type="call-destination">
<extensionElements>
<mbu:destination>my-destination-code</mbu:destination>
<mbu:method>POST</mbu:method>
<mbu:path>/api/resource/${processInput.resourceId}</mbu:path>
<mbu:header name="Content-Type" value="application/json"/>
<mbu:header name="X-Tenant" value="${processInput.tenantId}"/>
<mbu:payload>{"amount": ${processInput.amount}}</mbu:payload>
</extensionElements>
</serviceTask>
Output variables (auto-created for task id myTask):
| Variable | Type | Content |
|---|---|---|
myTask_status | Integer | HTTP response status code |
myTask_response | Map | Parsed JSON response body |
myTask_responseRaw | String | Raw response body string |
Access nested fields with dot notation: ${myTask_response.data.id}.
Optional: callback declaration — see Inbound callbacks below.
assign-variable — set process variables
Sets one or more process-scoped variables. Values are JUEL expressions evaluated at execution time.
<serviceTask id="setVars" name="Set Variables" mbu:type="assign-variable">
<extensionElements>
<mbu:variable name="loanId" value="${processInput.loanId}"/>
<mbu:variable name="fee" value="${processInput.baseFee * 1.2}"/>
<mbu:variable name="counter" value="${counter == null ? 0 : counter + 1}"/>
</extensionElements>
</serviceTask>
build-json — construct a JSON variable
Builds a JSON string variable by merging a template with a values map. Use this to construct API payloads or the final processResult.
<serviceTask id="buildPayload" name="Build Payload" mbu:type="build-json">
<extensionElements>
<mbu:jsonTemplate>{"clientId": "${c}", "amount": ${a}, "currency": "${cur}"}</mbu:jsonTemplate>
<mbu:jsonValues>{"c": "${clientId}", "a": "${amount}", "cur": "${currency}"}</mbu:jsonValues>
<mbu:resultVariable>apiPayload</mbu:resultVariable>
</extensionElements>
</serviceTask>
mbu:jsonTemplate must resolve to a JSON object ({...}) at the root. A root-level array is not supported, and fails at runtime with an unhelpful, low-level error.
Works:
{"clientId": "abc123", "amount": 500}
{"operations": [{"op": "add", "path": "/foo", "value": "bar"}]}
Does not work:
[{"op": "add", "path": "/foo", "value": "bar"}]
["abc123", "def456"]
If you need an array as the payload (for example, a JSON Patch body), wrap it in an object and reference the array field:
<mbu:jsonTemplate>{"operations": [${ops}]}</mbu:jsonTemplate>
<mbu:jsonValues>{"ops": "..."}</mbu:jsonValues>
<mbu:resultVariable>patchPayload</mbu:resultVariable>
Then pass ${patchPayload.operations} downstream, or reference ${patchPayload} as a whole if the consuming call accepts the wrapper object.
The result is stored as a string in apiPayload. Pass it as a <mbu:payload> in a subsequent call-destination task: <mbu:payload>${apiPayload}</mbu:payload>.
To set the process output returned to the caller, set <mbu:resultVariable>processResult</mbu:resultVariable>.
parse-json — parse a JSON string into a Map
Converts a raw JSON string variable into a traversable Map (or List, for a JSON array).
parse-json can parse a root-level JSON array into a List, but build-json cannot produce one — see the limitation note above.
<serviceTask id="parseResponse" name="Parse Response" mbu:type="parse-json">
<extensionElements>
<mbu:input>${myTask_responseRaw}</mbu:input>
<mbu:outputVariable>parsedResponse</mbu:outputVariable>
</extensionElements>
</serviceTask>
After parsing, use dot notation: ${parsedResponse.data.id}, or index notation for an array: ${parsedResponse[0].name}.
businessRuleTask — DMN decision table
Evaluates a DMN decision table deployed to the engine, referenced by its decision key.
<businessRuleTask id="applyRules" name="Apply Credit Rules">
<extensionElements>
<dmn:field name="decisionTableReferenceKey">
<dmn:string>creditScoringRules</dmn:string>
</dmn:field>
</extensionElements>
</businessRuleTask>
The <definitions> root element must also declare the xmlns:dmn="http://mambu.com/bpmn/dmn" namespace. The output of the DMN table is stored as process variables named after the DMN table's output columns.
Flow control
Exclusive gateway (XOR)
Routes execution to exactly one outgoing path based on conditions. Set a default sequence flow as the fallback when no condition matches.
<exclusiveGateway id="checkStatus" default="fallbackFlow"/>
<sequenceFlow sourceRef="checkStatus" targetRef="approvedTask">
<conditionExpression>${myTask_status == 201}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="fallbackFlow" sourceRef="checkStatus" targetRef="errorTask"/>
Use <![CDATA[...]]> for conditions containing < or >:
<conditionExpression><![CDATA[${amount > 10000}]]></conditionExpression>
Parallel gateway (AND)
Splits execution into all outgoing paths simultaneously. A matching join gateway waits for all branches to complete before continuing.
<!-- Split -->
<parallelGateway id="split"/>
<sequenceFlow sourceRef="split" targetRef="taskA"/>
<sequenceFlow sourceRef="split" targetRef="taskB"/>
<!-- Join (waits for both taskA and taskB) -->
<parallelGateway id="join"/>
<sequenceFlow sourceRef="taskA" targetRef="join"/>
<sequenceFlow sourceRef="taskB" targetRef="join"/>
Inclusive gateway (OR)
Routes execution to one or more outgoing paths where the condition is true. Like a parallel gateway for conditional multi-branch execution.
Human tasks
A <userTask> pauses the process and creates an item in the Mambu Tasks inbox. Execution resumes when the assignee completes the task via the Process Tasks API.
<userTask id="approveWithdrawal"
name="Approve Counter Withdrawal"
mbu:assignedUser="${processInput.approverUsername}"
mbu:priority="50"
mbu:dueDate="P1D"
mbu:asyncLeave="true"
mbu:taskLinkType="DEPOSIT_ACCOUNT"
mbu:taskLinkId="${processInput.depositAccountId}"
mbu:template="WithdrawalApproval"
mbu:replySchema="withdrawalReplySchema"/>
User task attributes
| Attribute | Required | Description |
|---|---|---|
mbu:assignedUser | Yes | Username or JUEL expression resolving to a username |
mbu:asyncLeave | Yes | Must be "true" — mandatory on every userTask |
mbu:priority | No | Integer 0–100; higher values appear first in the inbox |
mbu:dueDate | No | ISO 8601 duration (P1D) or instant (2026-12-31T00:00:00Z) |
mbu:taskLinkType | No | CBE entity type for a deep link in the inbox (e.g. DEPOSIT_ACCOUNT) |
mbu:taskLinkId | No | Entity ID for the deep link |
mbu:template | No | Name of a TASK notification message template |
mbu:replySchema | No | External ID of a JSON Schema that validates the completion payload |
Completing a task
The assignee calls:
curl -X POST "https://YOUR_HOST/api/processtasks/{taskId}:complete" \
-H "Content-Type: application/vnd.mambu.v2+json" \
-u "APPROVER_USER:APPROVER_PASSWORD" \
-d '{"decision": "APPROVED", "comment": "Verified at counter"}'
Every field in the completion body is stored as a process variable prefixed by the task id. For a task with id approveWithdrawal:
| Completion field | Process variable |
|---|---|
decision | approveWithdrawal_decision |
comment | approveWithdrawal_comment |
Downstream gateways can read these directly: ${approveWithdrawal_decision == 'APPROVED'}.
Async execution
Fire-and-forget process start
Add mbu:async="true" to the <startEvent> to return 204 No Content to the caller immediately and continue execution on the engine's worker pool.
<startEvent id="start" mbu:async="true"/>
Without this attribute, the caller waits for the process to complete (synchronous, returns 200 OK with the processResult).
Async leave on individual tasks
mbu:asyncLeave="true" on a task causes the engine to commit the task's result to the database and resume from a fresh transaction. This is required on all <userTask> elements and is useful on long-running call-destination tasks to ensure their results are durable even if the engine node restarts.
Inbound callbacks
Use an <intermediateCatchEvent> with a <messageEventDefinition> to park the process until an external system delivers a callback. This pattern is used for async vendor integrations — AML screening, KYC checks, credit bureau lookups.
<!-- Declare the message -->
<message id="kycResultMessage" name="kyc_result"/>
<!-- Make the outbound call, declaring the expected callback message -->
<serviceTask id="requestKyc" mbu:type="call-destination">
<extensionElements>
<mbu:destination>kyc-vendor</mbu:destination>
<mbu:method>POST</mbu:method>
<mbu:path>/verify</mbu:path>
<mbu:payload>{"clientId": "${processInput.clientId}"}</mbu:payload>
<mbu:callback message="kyc_result"/>
</extensionElements>
</serviceTask>
<!-- Park here until the vendor delivers the callback -->
<intermediateCatchEvent id="awaitKycResult">
<messageEventDefinition messageRef="kycResultMessage"/>
</intermediateCatchEvent>
When <mbu:callback message="..."/> is declared, the engine automatically injects two HTTP headers onto the outbound request:
| Header | Value |
|---|---|
X-Callback-Execution-Id | The execution ID the vendor must include in the callback URL |
X-Callback-Message | The message name to deliver |
The vendor resumes the process by calling:
POST /api/processexecutions/{executionId}/messages/{messageName}:deliver
The callback payload is available as {catchEventId}_response (Map) and {catchEventId}_responseRaw (String) in subsequent steps.
The call-destination task with <mbu:callback> and the <intermediateCatchEvent> must share the same execution token. Do not split them across a parallel gateway — this is validated at deploy time.
Timer events
Timer start event
Starts the process on a schedule.
<!-- Fixed date/time -->
<startEvent id="start">
<timerEventDefinition>
<timeDate>2026-12-31T23:59:59Z</timeDate>
</timerEventDefinition>
</startEvent>
<!-- Recurring (cron) -->
<startEvent id="start">
<timerEventDefinition>
<timeCycle>0 0 2 * * ?</timeCycle>
</timerEventDefinition>
</startEvent>
Timer boundary event
Adds a timeout to a task. When the timer fires, execution leaves the task via the boundary event's sequence flow.
<userTask id="approveTask" name="Approve" mbu:asyncLeave="true" mbu:assignedUser="${approver}"/>
<!-- Non-interrupting: fires after 1 day but does not cancel the task -->
<boundaryEvent id="approvalTimeout" attachedToRef="approveTask" cancelActivity="false">
<timerEventDefinition>
<timeDuration>P1D</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow sourceRef="approvalTimeout" targetRef="sendReminder"/>
Set cancelActivity="true" (the default) to interrupt and cancel the task when the timer fires.
Timer intermediate catch event
Pauses execution for a fixed duration.
<intermediateCatchEvent id="wait30s">
<timerEventDefinition>
<timeDuration>PT30S</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
Error handling
Boundary error events
Attach a boundary error event to any <serviceTask> to catch HTTP errors (non-2xx responses) from a call-destination task, or any thrown error from other task types. The error variables ({taskId}_status, {taskId}_response) are still populated and available on the boundary path.
<serviceTask id="callApi" mbu:type="call-destination">
<!-- ... -->
</serviceTask>
<boundaryEvent id="apiError" attachedToRef="callApi">
<errorEventDefinition/>
</boundaryEvent>
<sequenceFlow sourceRef="apiError" targetRef="handleError"/>
Compensating transactions
Use compensation boundary events and compensation tasks to undo completed work when a later step fails. This is the BPMN equivalent of a saga pattern.
<!-- Mark the initial task as compensatable -->
<serviceTask id="createLoan" name="Create Loan" mbu:type="call-destination">
<!-- ... -->
</serviceTask>
<boundaryEvent id="compensateCreateLoan" attachedToRef="createLoan">
<compensateEventDefinition/>
</boundaryEvent>
<!-- Compensation handler: called when compensation is triggered -->
<serviceTask id="cancelLoan" name="Cancel Loan" isForCompensation="true" mbu:type="call-destination">
<!-- ... -->
</serviceTask>
<association sourceRef="compensateCreateLoan" targetRef="cancelLoan"/>
<!-- Trigger compensation from an error path -->
<intermediateThrowEvent id="triggerCompensation">
<compensateEventDefinition/>
</intermediateThrowEvent>
Expression language (JUEL)
Process variable values and BPMN condition expressions use JUEL (${...} syntax).
String operations
${processInput.firstName.concat(" ").concat(processInput.lastName)}
${myTask_response.status}
${"pending".equals(loanStatus)}
Numeric operations
${processInput.baseFee * 1.15}
${counter + 1}
${processInput.amount > 10000}
Map access
${myTask_response.data.id}
${myTask_response.rates.EUR}
Use get() for keys with special characters:
${myTask_response.get("client-id")}
List access
${myList.get(0)}
${myList.size()}
Null-safe defaults
${counter == null ? 0 : counter + 1}
${processInput.comment == null ? "No comment" : processInput.comment}
XML escaping
In XML attributes and element values, use < for < and > for >:
<conditionExpression>${amount < 5000}</conditionExpression>
Or wrap in CDATA:
<conditionExpression><![CDATA[${amount < 5000}]]></conditionExpression>
In <mbu:path> and <mbu:payload>, use & for & in query strings:
<mbu:path>/v1/latest?from=GBP&to=EUR</mbu:path>
FEEL script tasks
Use <scriptTask scriptFormat="feel"> for collection operations that JUEL cannot express — filtering, mapping, and aggregating lists.
<scriptTask id="sumAmounts" name="Sum Amounts" scriptFormat="feel">
<script>
sum(for item in loanList return item.amount)
</script>
<extensionElements>
<mbu:resultVariable>totalAmount</mbu:resultVariable>
</extensionElements>
</scriptTask>
The FEEL expression has read access to all current process variables. The result is stored in totalAmount.
Supported BPMN elements
| Element | Supported |
|---|---|
<startEvent> | Yes |
<endEvent> | Yes |
<serviceTask> | Yes |
<userTask> | Yes |
<scriptTask> (FEEL only) | Yes |
<businessRuleTask> (DMN) | Yes |
<exclusiveGateway> | Yes |
<parallelGateway> | Yes |
<inclusiveGateway> | Yes |
<subProcess> | Yes |
<callActivity> | Yes |
<boundaryEvent> (error, timer, compensate) | Yes |
<intermediateCatchEvent> (message, timer) | Yes |
<intermediateThrowEvent> (compensate) | Yes |
<sequenceFlow> with <conditionExpression> | Yes |
<association> (compensation) | Yes |
<message> | Yes |
| Manual tasks, receive tasks | No |
| Signal events | No |
| Data objects | No |
What's next
- Worked Examples — four complete patterns: sync HTTP, async fire-and-forget, inbound callback, and human approval loop
- Getting Started — step-by-step quickstart tutorial
- Process Orchestration API Overview — API categories and endpoint index