What's it all about?
I've worked on two serverless backends that look almost the same on paper. CDK, Lambda, EventBridge, a primary data store. Same company stack. Totally different orchestration.
One is an order lifecycle. Validate, check payment, hand off to a partner, wait for confirmation, wait for fulfillment. People and partners can pause that flow for days. That one uses Step Functions.
The other is vendor checks. An upstream record changes, you start a vendor check, the result comes back later via webhook, then other handlers write the result back or open an ops ticket. That one is an EventBridge event chain. No state machine.
Same AWS shape. Different job. This is how I decide which one to reach for.
The two pictures in my head
System 1: babysit this order until it's done
Think of one order as a ticket on a conveyor:
- Mark it processing
- Check payment
- Payment fails? Open an ops ticket and wait
- Payment passes? Send it to a fulfillment partner and wait for confirmation
- Wait again for shipped / delivered
- Or branch into reorder / cancel
Ops always asks "where is this order?" You need a straight answer. Starting the same order twice can mean a double charge or a double partner submit. So one running process per order matters.
That is a process I own. A state machine fits.
System 2: react when something happens
Think of a vendor check less like a conveyor and more like a newsroom:
- Upstream says the source record changed
- One worker fetches the latest record
- Another starts a vendor check and walks away
- Hours later a webhook says the check finished
- Separate workers write the result back, open a ticket, or write analytics
Nothing holds the whole graph open for three days. A timer ("if still pending after 24h, ticket it") and a correlation id are enough. New listeners can subscribe without rewriting the happy path.
That is a world I react to. An event chain fits.
Orchestration: Step Functions
A central state machine owns the order. It picks the next station, holds mid-flight state, waits, and resumes with a typed outcome.
EventBridge only starts the machine. Step Functions owns the rest:
Those pauses use WAIT_FOR_TASK_TOKEN. Cancels call StopExecution. Partner webhooks and ticket resolution call SendTaskSuccess / SendTaskFailure with a next step.
Mental model: one AWS execution equals one order's life. Open the console, see the station.
Choreography: event chain
No conductor. Each Lambda does one job, emits a fact, and others react. Correlation lives in the database, not in a machine.
Mental model: a timeline of facts plus idempotent handlers. No single AWS execution "is" the workflow. A workflow id in your store is.
Side by side
| Question | Order lifecycle (Step Functions) | Vendor checks (Event chain) |
|---|---|---|
| What's moving? | One order through stations | Many independent reactions to events |
| What does "wait" mean? | Freeze this order until a human/partner replies | Don't freeze anything. Webhook or timer arrives later |
| How do ops find status? | Open the execution | Look up workflow id + event history |
| Fan-out? | Mostly linear branches | Natural. One event, many listeners |
| Cancel mid-flight? | Stop the execution | Emit "canceled". Handlers no-op via idempotency |
| Duplicate start risk? | High (money / partner side effects) | Lower if every handler is idempotent |
When I reach for Step Functions
Most of these are true:
- One entity, known stations. Something like
Processing -> PaymentVerified -> SentToPartner -> Confirmed -> Fulfilled. - The flow must wait, then continue with context.
WAIT_FOR_TASK_TOKENis the tell. - Branches are product rules. "Fixed -> retry payment." "Not fixed -> cancel."
- Ops needs "where is order X?" in one place.
- Starting twice is a bug. Double submit / double charge.
EventBridge still starts the run and fans out notifications. The spine stays in the state machine.
When I reach for an event chain
Most of these are true:
- Handlers react to facts. "Check completed" means write back. "Check failed" means open a ticket. They don't need to own the whole journey.
- Waits are webhooks or timers. Schedule T+24h, correlate by id. No open execution for a day.
- You'll keep adding listeners. Analytics, metrics, another ticket type. Without rewriting the happy path.
- Failures should stay local. Writeback DLQs writeback. It shouldn't freeze the whole check.
- You already store correlation. Workflow records and dedup markers answer "have we done this?"
What I actually ship
Neither stays pure.
| System | Pattern in practice |
|---|---|
| Order lifecycle | EventBridge ignites, Step Functions runs, webhooks/tickets resume the machine, domain events for UI/analytics |
| Vendor checks | EventBridge chains steps, Scheduler for timeouts, SQS when a vendor needs backpressure, DB as the workflow ledger |
Default toolkit:
| Need | Tool |
|---|---|
| Announce / start / notify | EventBridge |
| Owned process with waits + branches | Step Functions |
| Something happened, whoever cares reacts | Event chain |
| Fire once at T+N | EventBridge Scheduler |
| Absorb bursts / isolate a flaky vendor | SQS worker |
Decision checklist
-
Paused for three days. What resumes it? A stored task token: Step Functions. A new inbound event any handler can consume: event chain.
-
Is "next step" one policy, or many side effects? One policy: Step Functions. Independent side effects: event chain.
-
Do we need one stoppable, inspectable execution? Yes: Step Functions. Correlation id + event trail is enough: event chain.
-
How often will we add a reaction without changing the happy path? Often: event chain (or emit events from Step Function steps).
-
Cost of a duplicate start? Dangerous: orchestrate + strong idempotency. Harmless if handlers are idempotent: event chain.
Closing
I use Step Functions when the product is the workflow: stations, human pauses, partner waits, branches ops must understand. Babysitting an order to completion.
I use an event chain when the product is reacting to the outside world: upstream changes, vendor webhooks, timeouts. A newsroom of facts with durable, idempotent handlers.
Drawing BPMN with "wait for partner": orchestrate.
Listing nouns that happened (RecordFetched, CheckCreated, CheckCompleted): choreograph.
Orchestrate the process you own. Choreograph the world you don't.