Skip to main content

Events and Signals

Stateway supports two ways to inject external events into running process instances: instance events (targeted at a single instance) and broadcast signals (fan-out to all waiting instances across the tenant).

Instance Events

Send an event to a specific process instance. The event advances any token currently in a waiting state for that instance.

Request:

curl -X POST https://api.stateway.io/v1/instances/{instance_id}/events \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"event_type": "payment_received",
"variables": {
"paymentId": "pay-001",
"amount": 1500,
"method": "credit_card"
}
}'

Response:

{
"data": {
"event_type": "payment_received",
"processed": true
}
}

The variables object is merged into the instance variables before the token advances.

Use Cases

  • A payment gateway confirms a successful charge
  • An async service task callback returns a result
  • An external API finishes a long-running operation
  • An IoT device reports a sensor reading

Error conditions

StatusCodeCause
404NOT_FOUNDInstance does not exist or belongs to another tenant
404NOT_FOUNDInstance has no waiting tokens
422PROCESS_ERRORInstance is in completed, terminated, or error state

Broadcast Signals

Broadcast a named signal to all instances across the tenant that are currently waiting for that signal. Useful for cross-instance notifications.

Request:

curl -X POST https://api.stateway.io/v1/events/signal/price_update \
-H "X-API-Key: sw_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"variables": {
"productId": "prod-001",
"newPrice": 99.99
}
}'

Response:

{
"data": {
"signal_name": "price_update",
"processed": 3,
"failed": 0,
"errors": []
}
}

If processing a particular token raises an error, that token's failure does not abort the broadcast — the remaining tokens are still processed:

{
"data": {
"signal_name": "price_update",
"processed": 2,
"failed": 1,
"errors": [
{
"token_id": "9f2e1b3a-...",
"instance_id": "6c1d4f2a-...",
"message": "Variables payload size 5300021 bytes exceeds maximum 5242880 bytes (MAX_VARIABLES_PAYLOAD_BYTES)."
}
]
}
}

The processed field indicates how many waiting tokens were successfully processed without error (a token that was already completed by an earlier, idempotent broadcast retry is also counted here, since completeToken() no-ops rather than erroring). The variables are merged into each affected instance. failed counts tokens whose processing raised an error; the most common cause is the variables merge exceeding the size limit, in which case that token is reverted to dead and its instance transitions to error (recoverable via Token Move). Other, rarer causes (for example, a transient error reading the process definition) leave the token waiting and the instance untouched.

How Signals Work

  1. The engine queries all tokens with status = waiting across all instances of the tenant
  2. Tokens at elements expecting the signal are advanced
  3. The variables payload is merged into each instance's variables

Use Cases

  • Price updates affecting all pending orders
  • End-of-day batch signal to kick off nightly processes
  • Configuration changes that all running instances should acknowledge
  • Security events requiring immediate propagation across workflows

Instance Events vs. Broadcast Signals

AspectInstance EventBroadcast Signal
TargetOne specific instanceAll waiting instances in the tenant
Signal nameImplicit (advances any waiting token)Named (/events/signal/:signal_name)
VariablesMerged into that instanceMerged into every affected instance
EndpointPOST /instances/:id/eventsPOST /events/signal/:signal_name
Use caseCallbacks, async completionsGlobal notifications, fan-out

Async Service Task Callback

The most common pattern is completing an async service task. The BPMN configures executionMode: async with a callbackEvent name, and the external service POSTs to the instance event endpoint when done:

<bpmn:serviceTask id="processPayment" name="Process Payment">
<bpmn:extensionElements>
<stateway:taskDefinition type="http" />
<stateway:taskHeaders>
<stateway:header key="url" value="https://payments.example.com/charge" />
<stateway:header key="executionMode" value="async" />
<stateway:header key="callbackEvent" value="payment_completed" />
</stateway:taskHeaders>
<stateway:ioMapping>
<stateway:output source="transactionId" target="variables.transactionId" />
</stateway:ioMapping>
</bpmn:extensionElements>
</bpmn:serviceTask>

When the payment processor finishes, it calls:

Request:

curl -X POST https://api.stateway.io/v1/instances/{instance_id}/events \
-H "X-API-Key: sw_live_..." \
-H "Content-Type: application/json" \
-d '{
"event_type": "payment_completed",
"variables": { "transactionId": "tx-123", "status": "success" }
}'

Response:

{
"data": {
"event_type": "payment_completed",
"processed": true
}
}

Best Practices

  • Use descriptive event namespayment_received not event1
  • Include context in variables — always include IDs needed for traceability
  • Handle missing instances — sending an event to a completed or non-existent instance returns 404
  • Use broadcast sparingly — signals affect all waiting instances; make sure they are designed to handle the signal
  • Idempotency — the same event sent twice may advance a token twice if the instance returns to a waiting state; design with this in mind