Changelog — v0.5
Release Date: September 2026
Changed: Instance Events Only Resume the Element That Declares the Name
POST /v1/instances/:id/events and the Frontend Gateway's sendEvent now resume only the waiting elements that declare the event name: a catch event, a message boundary event, or an async service task's callbackEvent. Before, an event with a name that no element declared, or with no name at all, completed some other waiting token instead, which could be a user task. Through sendEvent, an end user could skip another role's task without claiming it. A user task is now completed only through the Tasks API or completeTask.
Behavior changes:
- An event whose name no waiting element declares is recorded for a later catch event, and nothing else advances (
processed: false). - An event sent to the REST API without
event_typeadvances nothing and returns202withprocessed: false. It used to complete the first waiting token, or return404when nothing was waiting. sendEventrequireseventName. When it resumes a catch or message boundary event, the process continues with the sender's session.- An async service task must declare
callbackEvent: publishing one without it now fails with400. Definitions published before keep working, but their async tasks withoutcallbackEventcan no longer be resumed by an event and end at theirtimeout. AddcallbackEventand publish them again, sending its name inevent_type.
See Which token does the event resume?.
Changed: Who Receives notification and instance.completed
Notifications and instance.completed now go to the instance's participants: the user who started it with startProcess and every user who is the assignee of one of its tasks, whether the task is open, completed or cancelled. An agent who claimed a task now receives the notifications of that instance, for example the reason a customer gave for cancelling. Before, the recipients depended on how the instance was started: an agent with a claimed task got no notifications when the customer started the instance through the Frontend Gateway, and a candidate who never claimed a task could get them when the instance was started through the REST API.
Behavior change: candidates who never claimed a task no longer receive notifications or instance.completed. Use task.cancelled and task.completed to keep a task list up to date. See Who Receives a Notification.
New: task.cancelled in the Frontend Gateway
When a task stops existing before anyone completes it, the Frontend Gateway now sends task.cancelled { taskId, instanceId, reason } to every user who could see it, the same users who would receive its task.completed, including whoever claimed it. Before, the task stayed on screen until the instance ended or the user reconnected, and the user only found out when submitting the form. reason is one of boundary, scope, terminateEndEvent, callActivity, terminated or api. @stateway/frontend-sdk 0.7.0 adds the event, the TaskCancelledEvent type and an onTaskCancelled callback in onTaskFlow. See Task Events.
Fixed: cancelling a user task's token through DELETE /v1/instances/:id/tokens/:tokenId no longer marks an already completed task as cancelled.
New: Throw Events Fire Named Events in Their Own Instance
An intermediateThrowEvent or endEvent that declares an event name, through a messageEventDefinition with a messageRef or stateway:eventCorrelation, now throws that event to its own instance when a token reaches it. Waiting catch events and message boundary events of the instance react to it, the same as to POST /v1/instances/:id/events. Other instances are not affected. A process can now cancel the task of a parallel branch without an API call. The payload is the element's variableFilter, and the notification now carries eventName (@stateway/frontend-sdk 0.6.0). See Throwing Named Events from a Process.
An endEvent with a terminateEventDefinition now ends every other path of its scope and cancels their open tasks. At the top level the instance ends as completed; inside a sub-process only that sub-process ends. See Terminate End Event.
Behavior changes, for definitions published from this version on (definitions already published keep the previous behavior until they are published again):
- A message throw event with a
messageRefthat was used only as a notification now also throws the event named after the message. This affects the instance only if it has a catch or boundary event with that name. - A
terminateEventDefinitionused to be ignored, so the end event ended only its own path. It now ends the other paths of its scope. - Publishing now rejects an
endEventwith amessageEventDefinitionand no message name, a throw event that declares both a signal and an event name, and a terminate end event that also throws a signal or an event.
Changed: {{session.*}} Is the User Advancing the Process
{{session.userId}} and {{session.userDisplay}} now hold the identity of the Frontend Gateway user whose action is advancing the process, instead of a fixed copy of who started the instance:
startProcess: the user who started it, until the process stops at a task, timer or event.completeTask: the user who completed the task, in that task'sioMappingoutputs and in everything that runs until the process stops again.sendEvent: the user who sent the event.- REST API calls, timers, signals, messages and service task responses: empty.
Tasks can now record who acted, from the session rather than from variables the browser submits: <stateway:output source="{{session.userId}}" target="resolvedBy" /> on a userTask.
Behavior change: assignee="{{session.userId}}" on a task created after another user's task now resolves to the user who completed that task, not to the starter. To route a later task back to the starter, record it on the startEvent ({{session.userId}} → openedBy) and assign with {{variables.openedBy}}. A task created while startProcess runs is unaffected. session is no longer listed for the ioMapping outputs of serviceTask, sendTask and message or signal catch events; it was always the starter there and is now empty. See Assigning to Whoever Started the Instance.
New: instance_filter — Limit Which Instances a Session Reaches
Session scopes accept definitions[].instance_filter, a search filter your backend sets when it creates the session. The Frontend Gateway combines it with every search() filter the browser sends, so an end user can be limited to their own instances, for example { "variables.openedBy": "user_42" }. Previously search() returned the instances of every user of the tenant, and a filter sent from the browser was the only restriction. Without instance_filter the behavior is unchanged. Fill the owner variable from the session with {{session.userId}} in the start event ioMapping, not from a variable the browser sends. See Instance Filter.
Security fixes in the Frontend Gateway, with behavior changes:
search()withtextand notext.fieldsnow searches only thesearchable_variables. Before, it searched every variable, so a session could test values of variables it cannot see. Withsearchable_variables: [], text search is rejected withSCOPE_VIOLATION.search()rejects afilterthat is not an object withSCOPE_VIOLATION. A filter sent as an MQL string used to skip thesearchable_variablescheck.sendEvent()now requires the instance to belong to a definition in the session scope and to match itsinstance_filter. Before, it accepted any instance of the tenant. A rejected call returnsSCOPE_VIOLATION, the same as for an instance that does not exist.
Fixed: a search filter on the instance id ({ "id": "..." }) no longer fails with an ambiguous column error.
New: {{session.*}} — Who Started the Instance
Process expressions can now read the identity of the user who started an instance through the Frontend Gateway: {{session.userId}} and {{session.userDisplay}}. Stateway records both from the session your backend created, at the moment startProcess runs, and the browser cannot change them. They are available in assignment (assignee, candidateUsers, candidateGroups), sequence flow conditions, ioMapping sources and taskHeaders. A child instance started by a callActivity inherits them.
Instances started with POST /v1/instances have no session, so {{session.userId}} is empty there. A definition started through both channels can fall back explicitly: {{session.userId || variables.requesterId}}.
Variables sent by startProcess come from the end user's browser. An assignment such as assignee="{{variables.openedBy}}" lets any user who can start the process route its tasks to another user. Replace it with assignee="{{session.userId}}".
Behavior change: in the short form, a process variable named session is now shadowed by the namespace. Read it as {{variables.session}}.
See Assigning to Whoever Started the Instance.
New: ioMapping on startEvent and userTask
A startEvent can declare ioMapping outputs. They are applied once, when the instance starts, and can read the start variables, session, instance and now(). Use them to keep the starter as a regular variable, for search or display, without an extra element in the diagram: {{session.userId}} → openedBy. When the process start event declares outputs, only the mapped targets are written to the instance. Other variables sent by startProcess, POST /v1/instances or a callActivity are discarded, so the definition decides what enters the instance. This differs from Camunda 8, which always keeps the creation variables. A start event without outputs keeps every start variable. On a subProcess start event, outputs are added to the instance variables. inputs on a startEvent are rejected when the definition is published.
A userTask can declare ioMapping too. inputs become task variables when the task is created. outputs are applied on completion: when present, only the mapped targets reach the instance.
Previously, ioMapping on either element was rejected when the definition was published.
Fixed: startProcess Validates Typed Variables
startProcess in the Frontend SDK now validates the start variables against the definition's typed data objects and the variables payload limit, like POST /v1/instances already did, and rejects a variables value that is not an object. A rejected start returns VALIDATION_ERROR and creates no instance. On both, the check runs on the variables that reach the instance, after the start event mapping.
Changed: userTask Completion Honors the Declared dataOutputs
Completing a BPMN userTask that declares an ioSpecification now writes only the variables declared as dataOutput to the instance, and applies its dataOutputAssociations. This applies to POST /v1/tasks/{id}/complete and to completeTask() in the Frontend SDK. Previously every submitted variable was written, and dataOutputAssociations were ignored.
Behavior change: an ioSpecification that declares only dataInputs now writes nothing back on completion. If a later step reads a variable submitted with the task, declare it as a dataOutput of that task. A userTask without an ioSpecification is not affected. When a task declares ioMapping outputs, those decide what is written, as before.
completeTask() in the Frontend SDK now also validates the task's outputSets, like the REST API already did. A completion that fails them returns VALIDATION_ERROR, and the task is routed to its interrupting error boundary event, or the instance is marked error when there is none.
See BPMN Data Visibility.
See Recording who started the instance as a variable and Human Tasks — Mapping Inputs and Outputs.
Removed: GET /v1/monitoring/system
GET /v1/monitoring/system (cross-tenant aggregated metrics) is no longer available to tenants: no tenant API key, including keys with the admin scope, can read it, and the path now returns 404 NOT_FOUND. Cross-tenant metrics are reserved for platform operators. Use GET /v1/monitoring/summary to read the metrics of your own tenant.
Previously, any API key with the admin scope could call it.
Changed: candidateUsers Enforced on Claim and Delegate
POST /v1/tasks/{id}/claim and POST /v1/tasks/{id}/delegate now return 403 FORBIDDEN when a task's only assignment constraint is candidateUsers and the request's assignee is not in that list. The same check applies to the internal endpoint the Frontend Gateway calls when a session claims a task.
Assignment is a union: a task may be taken by its assignee, by anyone in candidateUsers, or by anyone in one of its candidateGroups. The REST API has no user-identity or group model, so it only rejects the one shape it can prove ineligible — a task with candidateUsers and neither an assignee nor any candidateGroups. For every other shape, verifying the candidate pool remains your application's responsibility. Through the Frontend Gateway, the full union is enforced against the session scope.
Previously, claim and delegate accepted any assignee.
See Human Tasks — Authorization & Segregation of Duties.
New: Filter Tasks by Candidate Group
GET /v1/tasks?candidate_group=<group> returns the tasks whose candidateGroups contains that group — useful for building a group inbox without scanning every task.
Changed: GET /v1/tasks?status= Rejects Unknown Values
status must be one of pending, claimed, completed or cancelled. An unrecognized value now returns 400 instead of silently returning an empty list.