External Event Step
Wait for external signals to resume workflow
The external event step pauses the workflow and waits for an external system to send a signal. This is how you integrate webhooks from third-party services, user actions, or any asynchronous event into a durable workflow.
Schema
| Field | Type | Required | Description |
|---|---|---|---|
type | "external_event" | Yes | Step type discriminator |
name | string (1-100 chars) | Yes | Unique step name within the workflow |
signalType | string | Yes | The signal name this step is waiting for |
timeoutMs | number | No | Maximum time to wait in milliseconds before failing |
retry | RetryPolicy | No | Retry policy for failed executions |
Configuration Example
{
"type": "external_event",
"name": "wait-for-payment",
"signalType": "payment_confirmed",
"timeoutMs": 86400000
}This step pauses the workflow and waits up to 24 hours for a payment_confirmed signal.
How It Works
Step execution
-
When the engine reaches an external event step, it calls
executeExternalEventStep. -
The handler returns a
waitingresult with thesignalTypeand an optionalwaitUntiltimeout:return { status: 'waiting', signalType: step.signalType, ...(step.timeoutMs && { waitUntil: new Date(Date.now() + step.timeoutMs), }), }; -
The engine transitions the step to
WAITINGand the workflow toWAITING. -
A
WORKFLOW_PAUSEDevent is recorded withreason: "external_event"and thesignalType. -
If
timeoutMsis set, a delayed BullMQ job is also scheduled as a timeout guard.
Resuming with a signal
To resume the workflow, send a signal via the resume API endpoint:
POST /v1/workflow-runs/{id}/resumeRequest body:
{
"signalType": "payment_confirmed",
"payload": {
"transactionId": "txn_abc123",
"amount": 99.00,
"currency": "USD"
}
}The signalType in the request must match the signalType the step is waiting for. The payload is merged into the workflow state so subsequent steps can access the signal data.
Response:
{
"success": true,
"data": {
"id": "run_xyz",
"status": "RUNNING",
"currentStepName": "wait-for-payment"
}
}Resume schema
| Field | Type | Required | Description |
|---|---|---|---|
signalType | string | Yes | Must match the step's signalType |
payload | Record<string, unknown> | No | Data to merge into workflow state |
Timeout Behavior
When timeoutMs is set, the engine schedules a delayed BullMQ job alongside the wait. If the timeout elapses before a signal arrives, the workflow handles it according to the retry policy:
- If retries remain, the step is retried (and enters the
WAITINGstate again). - If all retries are exhausted, the workflow transitions to
FAILED.
If a signal arrives before the timeout, the delayed timeout job is effectively a no-op -- the engine sees the step is no longer in WAITING status and skips processing.
Practical Usage
External event steps are useful for:
- Payment confirmation -- pause until a payment gateway sends a webhook
- Third-party callbacks -- wait for a background check, credit report, or API result
- User actions -- pause until a user clicks a link, fills out a form, or makes a choice
- Cross-system orchestration -- coordinate between microservices with signal-based handoffs
Example: order fulfillment with external events
{
"name": "order-fulfillment",
"version": "1.0",
"steps": [
{
"type": "task",
"name": "create-order",
"handler": "createOrder"
},
{
"type": "external_event",
"name": "wait-for-payment",
"signalType": "payment_confirmed",
"timeoutMs": 86400000
},
{
"type": "task",
"name": "fulfill-order",
"handler": "fulfillOrder"
},
{
"type": "external_event",
"name": "wait-for-shipping",
"signalType": "shipment_delivered",
"timeoutMs": 604800000
},
{
"type": "task",
"name": "send-review-request",
"handler": "requestReview"
}
]
}In this workflow, the fulfill-order step only runs after payment is confirmed, and the review request only goes out after the shipment is delivered. Each external event step resumes when the matching signal arrives via the API.