Skip to main content

BPMN Authoring Reference

Non-Production Preview

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>
AttributeRequired value
xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL
xmlns:mbuhttp://mambu.com/bpmn
targetNamespaceprocess-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​

authTypesecureProperties fieldsHow it works
BASICusername, passwordHTTP Basic Authorization header
API_KEYapiKey, headerNameNamed 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.

caution

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):

VariableTypeContent
myTask_statusIntegerHTTP response status code
myTask_responseMapParsed JSON response body
myTask_responseRawStringRaw 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>
Limitation: root must be a JSON object

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).

note

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​

AttributeRequiredDescription
mbu:assignedUserYesUsername or JUEL expression resolving to a username
mbu:asyncLeaveYesMust be "true" — mandatory on every userTask
mbu:priorityNoInteger 0–100; higher values appear first in the inbox
mbu:dueDateYesISO 8601 duration (P1D) or instant (2026-12-31T00:00:00Z). A <userTask> without it is rejected at deploy with error 16023
mbu:taskLinkTypeNoCBE entity type for a deep link in the inbox (e.g. DEPOSIT_ACCOUNT)
mbu:taskLinkIdNoEntity ID for the deep link
mbu:templateNoName of a TASK notification message template
mbu:replySchemaNoExternal 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 fieldProcess variable
decisionapproveWithdrawal_decision
commentapproveWithdrawal_comment

Downstream gateways can read these directly: ${approveWithdrawal_decision == 'APPROVED'}.


Async execution​

Async execution is set on individual BPMN elements (the start event, service tasks, user tasks), not on the process as a whole. Two attributes control it:

AttributeWhere the engine does the work
mbu:async="true"Before the element runs, the engine hands the work to its worker pool. The element then runs in the background, in its own transaction, and the API caller does not wait for it.
mbu:asyncLeave="true"After the element finishes, the engine saves its result to the database and continues the process in a new transaction.

Fire-and-forget process start​

To return 204 No Content to the caller immediately and continue execution on the engine's worker pool, set mbu:async="true" on the <startEvent>:

<startEvent id="start" mbu:async="true"/>

Without it, the caller waits for the process to complete (synchronous, returns 200 OK with the processResult). Other elements can also carry mbu:async="true", independently of the start event.

Async leave on individual tasks​

mbu:asyncLeave="true" on a task causes the engine to save the task's result to the database and continue the process in a new 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:

HeaderValue
X-Callback-Execution-IdThe execution ID the vendor must include in the callback URL
X-Callback-MessageThe 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.

note

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 &lt; for < and &gt; for >:

<conditionExpression>${amount &lt; 5000}</conditionExpression>

Or wrap in CDATA:

<conditionExpression><![CDATA[${amount < 5000}]]></conditionExpression>

In <mbu:path> and <mbu:payload>, use &amp; for & in query strings:

<mbu:path>/v1/latest?from=GBP&amp;to=EUR</mbu:path>

FEEL script tasks​

warning

Only FEEL script tasks are supported. General-purpose scripting (for example scriptFormat="javascript" or "groovy") is not available: a <scriptTask> with any other scriptFormat is rejected when the definition is deployed.

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​

ElementSupported
<startEvent>Yes
<endEvent>Yes
<serviceTask>Yes
<userTask>Yes
<scriptTask scriptFormat="feel">Yes (FEEL only)
<scriptTask> with any other script format (JavaScript, Groovy, and so on)No — rejected at deploy
<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 tasksNo
Signal eventsNo
Data objectsNo

What's next​