Timers
Timer events pause process execution until a specified time condition is met. Stateway supports three ISO 8601 timer patterns: duration, date, and cycle.
Timer Types
| Type | Format | Example | Description |
|---|---|---|---|
| Duration | PT[n]H[n]M[n]S | PT1H | Wait for a period of time |
| Date | ISO 8601 datetime | 2026-04-01T09:00:00Z | Wait until a specific date/time |
| Cycle | R[n]/PT[n]H | R3/PT1H | Repeat N times at a fixed interval |
ISO 8601 Duration Reference
PT1H → 1 hour
PT30M → 30 minutes
PT1H30M → 1 hour 30 minutes
P1D → 1 day
P2DT12H → 2 days 12 hours
Defining a Timer in a Process
Duration Timer
Wait 1 hour before continuing:
{
"id": "wait-approval",
"type": "timerEvent",
"name": "Wait 1 hour for approval",
"timerType": "duration",
"timerExpression": "PT1H",
"next": "check-approval"
}
Date Timer
Wait until a specific date:
{
"id": "start-q2",
"type": "timerEvent",
"name": "Start Q2 Campaign",
"timerType": "date",
"timerExpression": "2026-04-01T09:00:00Z",
"next": "launch-campaign"
}
Cycle Timer
Repeat 3 times every hour:
{
"id": "poll-status",
"type": "timerEvent",
"name": "Poll every hour, 3 times",
"timerType": "cycle",
"timerExpression": "R3/PT1H",
"next": "check-status"
}
Timer Events in BPMN XML
<intermediateCatchEvent id="timer1" name="Wait 1 hour">
<timerEventDefinition>
<timeDuration>PT1H</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
Managing Timer Jobs
List Active Timers
Request:
curl https://api.stateway.io/v1/timers \
-H "X-API-Key: sw_live_your_key"
Response:
{
"data": [
{
"id": "timer-abc123",
"instance_id": "inst-xyz789",
"element_id": "wait-approval",
"type": "duration",
"expression": "PT1H",
"fire_at": "2026-04-26T13:00:00.000Z",
"status": "scheduled",
"created_at": "2026-04-26T12:00:00.000Z"
}
]
}
Cancel a Timer
Request:
curl -X DELETE https://api.stateway.io/v1/timers/{timer_id} \
-H "X-API-Key: sw_live_your_key"
Response:
{
"data": {
"id": "timer-abc123",
"status": "cancelled"
}
}
Timers are created exclusively by the process engine when a definition is instantiated — there is no POST /timers endpoint. Use this DELETE endpoint to cancel a scheduled timer before it fires.
How Timers Work
When a process reaches a timerEvent:
- A timer job is created with a scheduled fire time
- The process token pauses at the timer element
- When the fire time is reached, the timer triggers and the token advances
All timer state is persisted. If the service restarts, pending timers automatically resume from where they left off.
This flow applies to startEvent and intermediateCatchEvent. A boundaryEvent timer also fires for real: it arms when the host activity (task, userTask, serviceTask, scriptTask, businessRuleTask, sendTask, receiveTask, subProcess, or callActivity — any other host type is rejected at publish time) enters waiting, and is cancelled automatically if the host completes first. By default (cancelActivity: true) it interrupts the host and routes through its own outgoing flow; with cancelActivity: false it opens a parallel branch without cancelling the host. A timeDate/timeDuration boundary timer fires once — a single point in time, not a repeatable occurrence. A non-interrupting timeCycle (R<n>/...) boundary re-arms itself after each occurrence, with the count decremented, until it runs out (R/..., with no count, never stops on its own); an interrupting one never re-arms, since firing it kills the host. See SLA Enforcement below for both patterns side by side.
Patterns
SLA Enforcement
Escalate if a task isn't completed within 48 hours:
{
"id": "user-review",
"type": "userTask",
"name": "Review (SLA: 48h)",
"next": "review-done"
},
{
"id": "sla-timer",
"type": "timerEvent",
"timerType": "duration",
"timerExpression": "PT48H",
"next": "escalate"
}
Use a parallel gateway to fork before the userTask and timer, and a join gateway to converge.
A boundaryEvent timer attached directly to the userTask does the same thing without the
parallel gateway, and is cancelled automatically once the task completes:
{
"id": "user-review",
"type": "userTask",
"name": "Review",
"outgoing": ["flow-ok"]
},
{
"id": "be-escalate",
"type": "boundaryEvent",
"attachedToRef": "user-review",
"cancelActivity": false,
"timerType": "duration",
"timerExpression": "PT48H",
"outgoing": ["flow-escalate"]
}
With cancelActivity: false the review stays open after 48h and the escalation branch runs in
parallel. Set it to true (the default) instead to cancel the review and route through the
escalation branch only.
Delayed Notification
Wait 24 hours after order before sending feedback request:
{
"id": "wait-24h",
"type": "timerEvent",
"timerType": "duration",
"timerExpression": "PT24H",
"next": "send-feedback-request"
}
Scheduled Batch Processing
Run a process every day at midnight:
{
"id": "daily-midnight",
"type": "timerEvent",
"timerType": "date",
"timerExpression": "2026-04-27T00:00:00Z",
"next": "run-batch"
}
Recurring Poll
Poll an external service 5 times, every 15 minutes:
{
"id": "poll-external",
"type": "timerEvent",
"timerType": "cycle",
"timerExpression": "R5/PT15M",
"next": "check-response"
}
Best Practices
- Use realistic durations — consider network latency and processing time
- Consider timezone handling — always use UTC in date timers
- Plan for recovery — timers survive restarts; design your processes accordingly
- Cancel unused timers — if a process branch is no longer relevant, cancel its timers
- Monitor timer jobs — use the list endpoint to track pending and overdue timers