Skip to main content

Events

Events represent something that happens during a process: a process starts, something is waited for, a result is emitted, an exception arises, time passes. Each event combines a position (start, end, intermediate catch, intermediate throw, boundary) with an event definition that says what kind of trigger or effect is involved.

Event definitions

The engine supports the following event definition types. Not every type is valid at every position.

DefinitionXML elementUsed for
None(no event-definition child)Plain start/end/throw points, the simplest form
MessagemessageEventDefinition messageRef="..."Send or wait for a correlated message
TimertimerEventDefinition with timeDate, timeDuration, or timeCycle childSchedule, wait, or fire on a cycle
SignalsignalEventDefinition signalRef="..."Broadcast or wait for a named signal
LinklinkEventDefinition name="..."In-process "goto" between throw and catch
ErrorerrorEventDefinition errorRef="..."Throw or catch a business error
EscalationescalationEventDefinition escalationRef="..."Throw or catch an escalation up the scope hierarchy
CompensationcompensateEventDefinitionTrigger or handle compensation for completed activities
ConditionalconditionalEventDefinition with condition childFire when a FEEL expression turns true
TerminateterminateEventDefinitionEnd the enclosing scope immediately

messageRef, signalRef, errorRef, and escalationRef must reference a top-level <message> / <signal> / <error> / <escalation> declaration in the same <definitions> block — see Global definitions.

Position × definition compatibility

StartEndIntermediate catchIntermediate throwBoundary
None
Message
Timer
Signal
Link
Errorevent sub-process only✓ (always interrupting)
Escalationevent sub-process only
Compensation
Conditionalevent sub-process only
Terminate

Compensation start events and compensation end events are not supported. Trigger compensation with a compensation intermediate throw event followed by a none end event, and handle it with a compensation boundary event attached to the activity to compensate.


Start events

Start events define how a process or sub-process begins.

Attributes

AttributeDefaultNotes
idRequired
nameOptional
isInterruptingtrueApplies to start events inside event sub-processes only. Ignored on top-level start events.

Auto-start behavior

When a process is deployed, typed start events are registered so an instance is started automatically when the trigger arrives:

DefinitionTrigger
NoneProcess started explicitly via API
MessageA message with the matching name is published
SignalA signal with the matching name is broadcast
TimerThe configured time arrives. Recurring expressions (R/PT1H) re-arm after each firing, one-shot (date) expressions fire once and deactivate

Re-deploying the process under a new version deactivates the previous version's auto-start registrations and arms the new ones.

Examples

<!-- None start event -->
<bpmn:startEvent id="start">
<bpmn:outgoing>f1</bpmn:outgoing>
</bpmn:startEvent>

<!-- Message start event -->
<bpmn:startEvent id="start-msg">
<bpmn:outgoing>f1</bpmn:outgoing>
<bpmn:messageEventDefinition messageRef="Msg_Payment" />
</bpmn:startEvent>

<!-- Recurring timer start (every hour) -->
<bpmn:startEvent id="start-timer">
<bpmn:outgoing>f1</bpmn:outgoing>
<bpmn:timerEventDefinition>
<bpmn:timeCycle xsi:type="bpmn:tFormalExpression">R/PT1H</bpmn:timeCycle>
</bpmn:timerEventDefinition>
</bpmn:startEvent>

<!-- Non-interrupting message start in an event sub-process -->
<bpmn:startEvent id="esp-start" isInterrupting="false">
<bpmn:outgoing>f1</bpmn:outgoing>
<bpmn:messageEventDefinition messageRef="Msg_Notify" />
</bpmn:startEvent>

Validation

CheckSeverity
Executable process has no start eventError
Start event has any incoming sequence flow (BPMN 2.0 §10.4.2)Error
Error / escalation start event placed outside an event sub-process (BPMN 2.0 §10.4.2 — these triggers are only legal as the starter of an event sub-process)Error
Compensation start event used in any positionError (not supported)
Conditional start event at top level — the engine does not auto-instantiate top-level conditional starts, use an event sub-process or external triggerWarning
messageRef / signalRef / errorRef / escalationRef references an unknown definitionError

End events

End events finish a path of execution. A process must have at least one reachable end event (or a node with no outgoing flow, which is treated as an implicit end).

Behavior by definition

DefinitionEffect
NoneCompletes the path normally
MessagePublishes a message, then completes. With quantum:taskDefinition set, a worker performs the dispatch, otherwise the engine publishes directly
ErrorThrows an error, propagates up the scope hierarchy to the nearest matching error boundary or error start event in an event sub-process
EscalationThrows an escalation up the scope chain to the nearest matching listener. With quantum:taskDefinition a worker performs the dispatch
TerminateCancels the nearest enclosing activity scope (sub-process, ad-hoc sub-process, or root process) and continues from that scope's outgoing flow. At the root, the entire instance terminates. A terminate inside a called process ends only the called process

Compensation end events are not supported. Use a compensation intermediate throw event followed by a none end event.

Examples

<!-- None end event -->
<bpmn:endEvent id="end">
<bpmn:incoming>f_last</bpmn:incoming>
</bpmn:endEvent>

<!-- Error end event -->
<bpmn:endEvent id="end-error">
<bpmn:incoming>f_err</bpmn:incoming>
<bpmn:errorEventDefinition errorRef="Err_Timeout" />
</bpmn:endEvent>

<!-- Terminate end event -->
<bpmn:endEvent id="end-terminate">
<bpmn:incoming>f_abort</bpmn:incoming>
<bpmn:terminateEventDefinition />
</bpmn:endEvent>

<!-- Message end event dispatched by a worker -->
<bpmn:endEvent id="end-msg">
<bpmn:extensionElements>
<quantum:taskDefinition type="send-notification" />
</bpmn:extensionElements>
<bpmn:incoming>f_notify</bpmn:incoming>
<bpmn:messageEventDefinition messageRef="Msg_Done" />
</bpmn:endEvent>

Validation

CheckSeverity
End event has any outgoing sequence flow (BPMN 2.0 §10.4.3)Error
Compensation end eventError (not supported)
messageRef / errorRef / escalationRef references an unknown definitionError
Message end event with no messageRef and no quantum:taskDefinitionWarning (publishes a nameless message)
Escalation end event with no escalationRef and no quantum:taskDefinitionWarning (throws a nameless escalation)

Intermediate catch events

Intermediate catch events pause the token until an external trigger arrives, then advance.

Behavior by definition

DefinitionBehavior
MessageWaits for a correlated message matching the referenced name. If a buffered message is already available it fires immediately
Timer — timeDurationWaits the specified ISO 8601 duration from the moment the event is reached
Timer — timeDateWaits until the specified ISO 8601 datetime
Timer — timeCycleFires repeatedly on the cycle
SignalWaits for a broadcast matching the referenced name, checks the buffer first
LinkReceives control from a matching link throw event of the same name in the same scope. Acts as a goto target

A catch event placed immediately after an event-based gateway must have exactly one incoming flow (from the gateway).

Examples

<!-- Message catch -->
<bpmn:intermediateCatchEvent id="catch-msg" name="Wait for Payment">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:messageEventDefinition messageRef="Msg_Payment" />
</bpmn:intermediateCatchEvent>

<!-- Timer catch (duration) -->
<bpmn:intermediateCatchEvent id="wait-5m" name="Wait 5 min">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:timerEventDefinition>
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">PT5M</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>

<!-- Signal catch -->
<bpmn:intermediateCatchEvent id="catch-signal" name="Receive Abort">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:signalEventDefinition signalRef="Sig_Abort" />
</bpmn:intermediateCatchEvent>

<!-- Link catch (jump target) -->
<bpmn:intermediateCatchEvent id="catch-link" name="Land">
<bpmn:outgoing>f3</bpmn:outgoing>
<bpmn:linkEventDefinition name="LinkA" />
</bpmn:intermediateCatchEvent>

Validation

CheckSeverity
No event definition presentError
Catch event has anything other than exactly one outgoing sequence flow (BPMN 2.0 §10.5.4 — intermediate events have one input, one output)Error
Catch event after an event-based gateway has more than one incoming flowError
messageRef / signalRef references an unknown definitionError

Intermediate throw events

Intermediate throw events emit something — a message, signal, escalation, link, or compensation trigger — and then advance immediately. A throw with no event definition is a pass-through.

Behavior by definition

DefinitionBehavior
NonePure pass-through
MessagePublishes the message with the current scope variables. If quantum:taskDefinition is also set, switches to external-job mode (see below)
SignalBroadcasts the signal to all subscribers
LinkTransfers control to the matching link catch in the same scope. Acts as a goto
EscalationThrows an escalation up to the nearest matching listener
CompensationTriggers compensation, activityRef can target a specific completed activity. When it targets a call activity, it runs the activity's own boundary handler if it has one, otherwise it propagates into the called process

Execution modes (apply to every throw kind)

A throw event with quantum:taskDefinition type="..." switches into worker-driven mode regardless of which event definition is attached: the engine creates a job, the worker performs the actual publish/throw, and the throw advances when the worker reports completion. In this mode the messageRef / escalationRef / etc. become optional — the worker controls what is dispatched.

ModeWhen activeBehavior
Direct dispatchNo quantum:taskDefinitionEngine publishes / throws immediately and advances
External jobquantum:taskDefinition type="..." is setEngine creates a persistent job, throw advances when the worker completes the job

The same applies to message and escalation end events.

Examples

<!-- Signal throw -->
<bpmn:intermediateThrowEvent id="throw-signal" name="Broadcast Abort">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:signalEventDefinition signalRef="Sig_Abort" />
</bpmn:intermediateThrowEvent>

<!-- Escalation throw -->
<bpmn:intermediateThrowEvent id="throw-esc" name="Escalate">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:escalationEventDefinition escalationRef="Esc_Review" />
</bpmn:intermediateThrowEvent>

<!-- Link throw (jump source) -->
<bpmn:intermediateThrowEvent id="throw-link" name="Jump">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:linkEventDefinition name="LinkA" />
</bpmn:intermediateThrowEvent>

<!-- Compensation throw — compensate everything completed in scope -->
<bpmn:intermediateThrowEvent id="throw-comp" name="Compensate All">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:compensateEventDefinition />
</bpmn:intermediateThrowEvent>

Validation

CheckSeverity
messageRef / signalRef / escalationRef references an unknown definitionError
Message throw with no messageRef and no quantum:taskDefinition (publish has no name to correlate against)Error
Escalation throw with no escalationRef, no inline escalationCode, and no quantum:taskDefinition (no listener can match)Error
Link throw has no matching link catch with the same name in the same scopeError
Compensation throw activityRef does not resolve to a known nodeError

Boundary events

Boundary events are attached to an activity (task, sub-process, or call activity) and fire while the activity is running.

Attributes

AttributeDefaultNotes
idRequired
nameOptional
attachedToRefRequired. ID of the host activity (must be in the same scope)
cancelActivitytruetrue = interrupting (host is cancelled, only the boundary path continues), false = non-interrupting (host continues, a parallel token starts from the boundary). Ignored on compensation boundaries

Behavior by definition

DefinitionNotes
MessageFires when a correlated message arrives while the host is active
TimertimeDuration and timeDate fire once. timeCycle (e.g. R3/PT1M) fires repeatedly and is non-interrupting only
ErrorAlways interrupting. Fires when the host throws a matching error
SignalFires when the matching signal is broadcast while the host is active
EscalationFires when the host throws a matching escalation
ConditionalFires when the FEEL condition evaluates to true while the host is active
CompensationMarks this boundary as the trigger for a compensation handler. Linked to the handler activity by an <association> element. Does not use cancelActivity — fires only when compensation is explicitly thrown for the host (after the host has completed), and never cancels the host

Examples

<!-- Interrupting timer boundary: 30-minute timeout -->
<bpmn:boundaryEvent id="timer-boundary" attachedToRef="long-task" cancelActivity="true">
<bpmn:outgoing>f_timeout</bpmn:outgoing>
<bpmn:timerEventDefinition>
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">PT30M</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

<!-- Non-interrupting message boundary: send a reminder while waiting -->
<bpmn:boundaryEvent id="msg-boundary" attachedToRef="approval-task" cancelActivity="false">
<bpmn:outgoing>f_notify</bpmn:outgoing>
<bpmn:messageEventDefinition messageRef="Msg_Reminder" />
</bpmn:boundaryEvent>

<!-- Error boundary: handle a known failure type -->
<bpmn:boundaryEvent id="err-boundary" attachedToRef="service-task" cancelActivity="true">
<bpmn:outgoing>f_error</bpmn:outgoing>
<bpmn:errorEventDefinition errorRef="Err_Timeout" />
</bpmn:boundaryEvent>

<!-- Compensation boundary, with handler linked by association -->
<bpmn:boundaryEvent id="comp-boundary" attachedToRef="book-hotel">
<bpmn:compensateEventDefinition />
</bpmn:boundaryEvent>
<bpmn:serviceTask id="cancel-hotel" name="Cancel Hotel" isForCompensation="true">
<bpmn:extensionElements>
<quantum:taskDefinition type="cancel-hotel-worker" />
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:association id="assoc1" sourceRef="comp-boundary" targetRef="cancel-hotel" />

<!-- Cyclic non-interrupting timer: fire every minute, up to 3 times -->
<bpmn:boundaryEvent id="cycle-boundary" attachedToRef="long-task" cancelActivity="false">
<bpmn:outgoing>f_tick</bpmn:outgoing>
<bpmn:timerEventDefinition>
<bpmn:timeCycle>R3/PT1M</bpmn:timeCycle>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>

Validation

CheckSeverity
attachedToRef is empty or doesn't resolve to an activity in the same scopeError
No event definition presentError
Error boundary has cancelActivity="false"Error
Timer boundary has none of timeDuration/timeDate/timeCycleError
Timer boundary uses timeCycle while interruptingError
Timer boundary mixes timeCycle with timeDuration or timeDateWarning (timeCycle wins)
Conditional boundary has empty conditionError
Compensation boundary has no associated handler activityError
Compensation handler is not marked isForCompensation="true"Error
messageRef / signalRef / errorRef / escalationRef references an unknown definitionError

FAQ

What's the difference between a BPMN message event and a signal event?

A message event is point-to-point and correlated by both message name AND a correlation key — when a publisher's name and key both match a subscriber's, the engine selects exactly one matching subscription and delivers the message to it. A signal event is a broadcast that matches on name only: every subscriber listening on the same signal name receives it. Use messages to route to a specific running instance (for example, the payment confirmation for order #123), and signals when multiple parts of one or more processes need to react to the same external occurrence.

How does message correlation work in QuantumBPM?

A non-start message event declares a correlation key with <quantum:subscription correlationKey="..."/> (or the zeebe:subscription alias), a FEEL expression. When a message catch event, boundary event, or receive task is reached, the engine evaluates that expression against the running instance's scope variables and registers a subscription tagged with the resulting value. A publisher — whether an internal message throw, send task, or message end event, or an external API call — submits the message with its own correlation value. The engine matches by name AND by correlation value using subset/containment semantics: a subscriber listening on {orderId: 123, region: "EU"} matches a publisher narrowing on {orderId: 123}, but not the other way around. Exactly one matching subscriber is selected and signalled per publish (BPMN 2.0 §13.2 exactly-once point-to-point delivery). Matching is type-sensitive — the number 123 never matches the string "123". Process-root message start events are the exception: they correlate by name alone, since they instantiate new processes rather than route to a running one.

What's the difference between an error event and an escalation event?

Error events represent a failure that must abort the current activity scope — they are caught by an interrupting error boundary event or by an error start event in an event sub-process, and they always cancel the throwing activity. Escalation events are softer notifications that travel up the scope hierarchy and can be caught by either interrupting or non-interrupting boundaries, the throwing activity does not need to be cancelled. Rule of thumb: error means an exception that must be handled, escalation means something noteworthy happened that someone should know about.

What is the difference between an interrupting and a non-interrupting boundary event?

An interrupting boundary event (cancelActivity="true", the default) cancels the host activity when it fires, execution continues only down the boundary's outgoing flow. A non-interrupting boundary event (cancelActivity="false") leaves the host running and starts a parallel branch from the boundary — typical for sending reminders or escalations while a long-running task continues. Error boundaries are always interrupting, the engine rejects non-interrupting error boundaries at validation.

Can a BPMN start event have an incoming sequence flow?

No. A start event must not have any incoming sequence flow — the modeler and the engine both reject this at deployment per BPMN 2.0 §10.4.2. If you need to re-enter a flow from elsewhere in the process, use a link catch event as a goto target, or model the entry point as a sub-process called from a parent.

Can a BPMN end event have an outgoing sequence flow?

No. An end event terminates a path of execution and cannot have outgoing flows — both the modeler and the engine reject this at deployment per BPMN 2.0 §10.4.3. If you need follow-up behaviour after a particular branch finishes, place an intermediate throw event (or a regular task) before the end event instead.

What does a terminate end event do?

A terminate end event immediately cancels every still-running token within the nearest enclosing activity scope — the sub-process, ad-hoc sub-process, or root process that contains the terminate. Execution then continues from that scope's outgoing flow. At the root, the entire process instance terminates. A terminate placed inside a called process ends only the called process, not its caller.

What are the different BPMN timer event expressions and what do they do?

timeDuration is an ISO 8601 duration (PT5M, PT30S) and waits the specified amount of time from the moment the event is reached. timeDate is an ISO 8601 datetime (2026-12-31T23:59:00Z) and waits until that exact moment. timeCycle is a recurring expression (R/PT1H for unbounded, R3/PT1M for a fixed number of repeats) that fires repeatedly. Cyclic timers are only valid on non-interrupting timer boundary events and on event-sub-process start events — interrupting boundaries with timeCycle are rejected at deployment.

What happens if a message arrives before its intermediate catch event is reached?

The message is buffered for up to one hour by default. When the token later reaches the catch event, the engine first checks the buffer for a matching message — same name, matching correlation key — and consumes it immediately if found, otherwise the catch registers a subscription and waits for a future publish. The same applies to signals (though signals match by name only). This makes processes robust to network-induced reordering: a downstream catch event correctly receives a message that physically arrived during an upstream task.

What's the difference between an error end event and an error boundary event?

An error end event throws a business error from a path inside an activity. An error boundary event, attached to the enclosing activity (a sub-process or service task), catches an error of a matching type and routes execution down its outgoing flow while cancelling the host activity. They are used together: throw with an error end event inside the activity, catch with an error boundary on the outside. Both reference the same <error> declaration through errorRef.

Can a timer boundary event use a cycle expression?

Only on a non-interrupting boundary. The engine and the modeler reject timeCycle on an interrupting timer boundary at deployment, because a cyclic interrupting boundary would cancel its host after the first tick and leave no host to interrupt on subsequent ticks. Use timeCycle with cancelActivity="false" to fire repeatedly while the host keeps running — the typical use case is sending periodic reminders during a long-running user task.

Can an error boundary event be non-interrupting?

No. Error boundary events are always interrupting — they cancel the host activity when they fire. The modeler rejects cancelActivity="false" on an error boundary at validation. If you need a non-cancelling reaction to an error condition, throw a signal or escalation event from inside the host activity's flow and catch it on the outside with a non-interrupting boundary instead.