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.

When work can happen twice

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

Pass an idempotency key

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:
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 picks up after a crash, but not always a replay made days later.

Write with an upsert

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.

Draw IDs in their own step

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.

Check before you act

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.

Start runs and emit 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 picks up. Place them in a step, so they happen once:

One run per order

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.