Skip to main content
A saved step never runs again. Yet the work inside a step can still happen twice, for example when a crash cuts the step off just after it charged a card, but before its result was saved. For work that leaves a mark outside your app, like a payment, an email, or a row in your database, make that second time harmless. This property is called idempotency, and this page shows the ways to get it.

Duplicate execution

The first two cases only touch the step that was cut off: the steps saved before it return their saved results.

Idempotency keys

Many services accept a key with each request, and ignore a second request with a key they’ve already seen. Payment and email providers commonly do. Build the key from your data, so every attempt sends the same one:
nelkap/workflows/cancel-order.ts
If the step runs again, Stripe sees the same key and returns the first refund instead of making a second one.
A provider keeps a key for a limited time, at least 24 hours at Stripe. That covers a step that resumes after a crash, but not always a replay made days later.

Upserts

In your own database, give each row a key that says what it is, like a contact’s email address, and insert or update in one statement:
The first attempt adds the contact, and a second one updates the same row instead of adding another.

Generated IDs

When your code makes up an ID for something it creates, draw the ID in a step of its own, then create the thing with it:
The ID is saved, so a second attempt of the invoice step uses the same one. createInvoice can then insert with ON CONFLICT (id) DO NOTHING, or pass the ID as an idempotency key.

Existence checks

When a service takes no key, look for what a first attempt left before you act:
A second attempt finds the ticket the first one created, and returns it.

Runs and events in steps

Code outside steps runs again on every call of the run. A run or an emit placed there would start a new run, or emit a new event, each time the run resumes. Place them in a step, so they happen once:

Duplicate runs

Some services send the same webhook twice, and your code may emit the same event twice. Each one starts its own run. When a run must happen once per order, per user, or per payment, let the first run claim it in your database, and let the others stop there. The order fulfillment example shows how.
A replay runs every step again, including the ones that charged cards or sent emails. Before you replay a run, check that its steps with side effects are idempotent.