> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nelkap.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Know when work can happen again, and make sure it leaves a single mark: one charge, one email, one row.

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

| What happens | What runs again |
| - | - |
| A crash, a deploy, or a host's time limit cuts a step off before its result is saved. | That step, from its start, when the run picks up. |
| The SDK can't reach Nelkap to save a step's result. | That step, when the run picks up about five minutes later. |
| You [replay](https://docs.nelkap.com/monitor/runs.md#replay-a-run) a run. | Every step, since a replay is a new run. |
| An event is emitted twice, or a service calls your webhook twice. | The whole workflow, in a second run. |

The first two cases only touch the step that was cut off: the steps saved before it give back their 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:

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
await nelkap.step.run('refund', () =>
  stripe.refunds.create(
    { payment_intent: order.paymentIntentId },
    { idempotencyKey: `refund_${order.id}` },
  ),
);
```

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](https://docs.stripe.com/api/idempotent_requests). That covers a step that picks up 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:

```sql theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
INSERT INTO contacts (email, name) VALUES ($1, $2)
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;
```

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:

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
const invoiceId = await nelkap.step.run(
  'invoice id',
  () => crypto.randomUUID(),
);

await nelkap.step.run('invoice', () =>
  createInvoice({ id: invoiceId, orderId: order.id }),
);
```

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:

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
const ticket = await nelkap.step.run(
  'ticket',
  async () => {
    const existingTicket = await findTicketByOrder(
      order.id,
    );

    return existingTicket ?? createTicket(order.id);
  },
);
```

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

```ts theme={"theme":{"light":"light-plus","dark":"dark-plus"}}
await nelkap.step.run('invoice run', () =>
  nelkap.run(sendInvoice, order.id),
);

await nelkap.step.run('paid event', () =>
  nelkap.emit({
    event: 'order.paid',
    payload: order.id,
  }),
);
```

## 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](https://docs.nelkap.com/examples/stripe-orders.md) shows how.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.