Workflow steps and waits

Sleep and wait without holding compute

Pause for time or an external event while preserving workflow state and avoiding a long-running request.

A Workflow can pause for a day, a month, or until a human clicks a button. While it’s paused, it costs nothing. No Worker request, no JavaScript timer keeps it alive. The engine persists its position and resumes it when the wait ends.

Time-based waits are one line:

await step.sleep('wait for trial to end', '14 days')
await step.sleepUntil('send on launch morning', new Date('2026-09-01T08:00:00Z'))

Compare that with what it replaces: a cron job scanning a table of pending trials, state flags, and edge cases around restarts. Here the “what happens next” logic reads top to bottom in one function.

Event-based waits pause until something outside delivers a matching event:

const approval = await step.waitForEvent('wait for manager approval', {
  type: 'approval',
  timeout: '3 days',
})

The other side sends the event through the instance handle:

const instance = await env.REVIEW_WORKFLOW.get(reviewId)
await instance.sendEvent({ type: 'approval', payload: { approvedBy: userId } })

The type must match the waitForEvent call, and the sender needs the instance ID. That’s why you give instances stable IDs. Create the workflow with id: reviewId and the approval endpoint can always address the right instance, with no lookup table.

Events are input, so treat them as untrusted

The event payload arrives from your endpoint, and your endpoint is reachable. Before acting, validate the event type, the tenant, the state, and replay.

Is this approver allowed to approve this tenant’s review? Is the workflow actually waiting? Has this approval already been consumed? A duplicate approval event should be recognized and ignored, not processed twice.

Plan for the event that never comes

waitForEvent throws when its timeout expires. The default is 24 hours. Catch it and decide: escalate, auto-reject, or send a reminder.

A workflow with no timeout plan doesn’t fail. It just waits, invisibly, forever. That’s worse than failing, because nothing tells you.

Try this: build a review workflow that waits for approval, times out after a practice interval, and rejects duplicate approval events. Use a short timeout like two minutes, so you can watch all three paths run: approved, timed out, duplicate.

Lesson completed