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
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | Instance does not exist or belongs to another tenant |
404 | NOT_FOUND | Instance has no waiting tokens |
422 | PROCESS_ERROR | Instance 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
- The engine queries all tokens with
status = waitingacross all instances of the tenant - Tokens at elements expecting the signal are advanced
- The
variablespayload 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
| Aspect | Instance Event | Broadcast Signal |
|---|---|---|
| Target | One specific instance | All waiting instances in the tenant |
| Signal name | Implicit (advances any waiting token) | Named (/events/signal/:signal_name) |
| Variables | Merged into that instance | Merged into every affected instance |
| Endpoint | POST /instances/:id/events | POST /events/signal/:signal_name |
| Use case | Callbacks, async completions | Global 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 names —
payment_receivednotevent1 - 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