# Handling idempotency
Source: https://www.inngest.com/docs/guides/handling-idempotency

Ensuring that your code is idempotent is foundational to building reliable systems. Within Inngest, there are multiple ways to ensure that your functions are idempotent.

## What is idempotency?

Idempotency, by definition, describes an operation that can occur multiple times without changing the result beyond the initial execution. In the world of software, this means that a functions can be executed multiple times, but it will always have the same effect as being called once. An example of this is an "upsert."

## How to handle idempotency with Inngest

It should always be the aim to write code that is idempotent itself within your system or your Inngest functions, but there are also some features within Inngest that can help you ensure idempotency.

As Inngest functions are triggered by events, there are two main ways to ensure idempotency:

* [at the event level (_the producer_)](#at-the-event-level-the-producer) and/or
* [at the function level (_the consumer_)](#at-the-function-level-the-consumer)

{/*TODO - New graphic in similar design style ![Relay graphic](/assets/docs/platform/replay/featured-image.png) */}

## At the event level (the producer)

Each event that is received by Inngest will trigger any functions with that matching trigger. If an event is sent twice, Inngest will trigger the function twice. This is the default behavior as Inngest does not know if the event is the same event or a new event.

<Callout>
**Example:** Using an e-commerce store as an example, a user can add the same t-shirt to their cart twice because they want to buy two (_2 unique events_). That same user may check out and pay for all items in their cart but click the "pay" button twice (_2 duplicate events_).
</Callout>

To prevent an event from being handled twice, you can set a unique event `id` when [sending the event](/docs/reference/events/send#inngest-send-event-payload-event-payload-promise). This `id` acts as an idempotency key **over a 24 hour period** and Inngest will check to see if that event has already been received before triggering another function.

<CodeGroup>

```ts
'CGo5Q5ekAxilN92d27asEoDO';
await inngest.send({
  id: `checkout-completed-${cartId}`, // <-- This is the idempotency key
  name: 'cart/checkout.completed',
  data: {
    email: 'taylor@example.com',
    cartId: cartId
  }
})
```

```go {{ title: "Go" }}
cart_id := "CGo5Q5ekAxilN92d27asEoDO"
inngest.Send(context.Background(), inngestgo.Event{
  ID: fmt.Sprintf("checkout-completed-%s", cart_id), // <-- This is the idempotency key
  Name: "cart/checkout.completed",
  Data: map[string]any{"email": "taylor@example.com", "cart_id": cart_id},
})
```

```python {{ title: "Python" }}
cart_id = 'CGo5Q5ekAxilN92d27asEoDO'
await inngest.send({
  id: f'checkout-completed-{cart_id}', // <-- This is the idempotency key
  name: 'cart/checkout.completed',
  data: {
    email: 'taylor@example.com',
    cart_id: cart_id
  }
})
```

</CodeGroup>

| Event ID | Timestamp | Function |
| -------- | --------- | -------- |
| `checkout-completed-CGo5Q5ekAxilN92d27asEoDO` | 08:00:00.000 | ✅ Functions are triggered |
| `checkout-completed-CGo5Q5ekAxilN92d27asEoDO` | 08:00:00.248 | ❌ Nothing is triggered |

As you can see in the above example, setting the `id` allows you to prevent duplicate execution on the producer side, where the event originates.

Some other key points to note:

* Event IDs will only be used to prevent duplicate execution for a 24 hour period. After 24 hours, the event will be treated as a new event and will trigger any functions with that trigger.
* Inngest will store the second event and it will be visible in your event history, but it will _not_ trigger any functions.
* Events that fan-out to multiple functions will trigger each function as they normally would.

{/* TODO - Link to revised fan-out guide above when complete */}

<Callout>
  **Tip** - If you are using Inngest's [webhook transforms](/docs/platform/webhooks#defining-a-transform-function), you can set the `id` in the transform to ensure that the event is idempotent.
</Callout>

<Callout variant="warning">
  Event idempotency is ignored by some features:
  - Debouncing
  - Event batching
  - Function pausing. While a function is paused, event idempotency is ignored. So if a replay is created after unpausing, it may have "skipped" runs that ignored event idempotency.
</Callout>

{/*### Uniqueness of event IDs

TODO - Highlight the importance of the uniqueness of the event ID across events that may have different names*/}

## At the function level (the consumer)

You might prefer to ensure idempotency at the function level or you may not be able to control the event that is being sent (from a webhook). The [function's `idempotency` config option](/docs/reference/functions/create#inngest-create-function-configuration-trigger-handler-inngest-function) allows you to do this.

Each function's `idempotency` key is defined as a [CEL expression](/docs/guides/writing-expressions) that is evaluated with the event payload's data. The expression is used to generate a unique string key which idempotently prevents duplicate execution of the function.

Each unique expression will only trigger one function execution **per 24 hour period**. After 24 hours, a new event that generates the same unique expression will trigger another function execution.

### Example

We'll use the same example of an e-commerce store to demonstrate how this works. We have an event here with no `id` set ([see above](#at-the-event-level-the-producer)), but we want to ensure that the `send-checkout-email` function is only triggered once for each `cartId` to prevent duplicate emails being sent.

<Row>
  <Col>

```json {{ title: "Event payload"}}
{
  "name": "cart/checkout.completed",
  "data": {
    "email": "blake@example.com",
    "cartId": "s6CIMNqIaxt503I1gVEICfwp"
  },
  "ts": 1703275661157
}
```

  </Col>
  <Col>

```ts {{ title: "Function definition with idempotency key"}}
sendEmail = inngest.createFunction(
  {
    id: 'send-checkout-email',
    // This is the idempotency key
    idempotency: 'event.data.cartId',
    // Evaluates to: "s6CIMNqIaxt503I1gVEICfwp"
    // for the given event payload
  },
  { trigger: 'cart/checkout.completed' },
  async  ({ event, step }) => { /* ... */ }
})
```

  </Col>
</Row>

### Writing CEL expressions

While CEL can do many things, we'll focus on how to use it to generate a unique string key for idempotency. The key things to know are:

* You can access any of the event payload's data using the `event` variable and dot-notation for nested properties.
* You can use the `+` operator to concatenate strings together.

Combining two or more properties together is a good way to ensure the level of uniqueness that you need. Here are couple of examples:

* **User signup:** You only want to send a welcome email once per user, so you'd set `idempotency` to `event.data.userId` in case there your API sends duplicate events.
* **Organization team invite:** A user may be part of multiple organizations in your app. You only want to send a team invite email once per user/organization combination, so you'd set `idempotency` to `event.data.userId + "-" + event.data.organizationId`.

For more information on writing CEL expressions, read [our guide](/docs/guides/writing-expressions).

<Callout>
  💡 If you want to control when a function is executed over a period of time you might prefer:

  * [`rateLimit`](/docs/reference/functions/rate-limit) - Limit the number of function executions per period of time
  * [`debounce`](/docs/reference/functions/debounce) - Delay function execution for duplicate events over a period of time
</Callout>

### Idempotency keys and fan-out

{/* TODO - This should link to a future iteration of the fan-out guide which is 1 event, n functions */}
One reason why you might want to use `idempotency` at the function level is if you have an `event` that fans-out to multiple functions. Let's take the following fan-out example:

| Function | Event trigger | How often |
| -------- | ------------- | --------- |
| Track requests | `ai/generation.requested` | Every time |
| Run generation | `ai/generation.requested` | Once per request |

In this case, you would want to set `idempotency` on the "Run generation" function to ensure that it runs once, for example, for every unique prompt that is sent. You may want to do this as you don't want to re-run the same exact prompt and waste compute resources/credits. However, you still might want to track the number of requests that each user submitted, so you would not want to set `idempotency` on the "Track requests" function. You can see the code for both functions below.

<details>
  <summary>**View the function code**</summary>

  Both functions use the same event trigger, `ai/generation.requested` which contains a `promptHash` and a `userId` in the event payload.

```ts {{ title: "Track requests function" }}
inngest.createFunction(
  { id: 'track-requests' },
  { event: 'ai/generation.requested' },
  async ({ event, step }) => {
    // Track the request
  }
)
```

```ts {{ title: "Run generation function" }}
inngest.createFunction(
  {
    id: 'run-generation',
    // Given the event payload sends a hash of the prompt,
    // this will only run once per unique prompt per user
    // every 24 hours:
    idempotency: `event.data.promptHash + "-" + event.data.userId`
  },
  { event: 'ai/generation.requested' },
  async ({ event, step }) => {
    // Track the request
  }
)
```

</details>