We use cookies

We use cookies to ensure you get the best experience on our website. For more information on how we use cookies, please see our cookie policy.

By clicking "Accept", you agree to our use of cookies.
Learn more.

GuideIdempotency

Idempotency

Idempotency is currently in beta and may be subject to change.

If you need to prevent more than one run of a task from occurring within a given time window, for instance because of duplicate event sends from a webhook that can trigger duplicate runs in Hatchet, you can achieve this by adding idempotency configuration to your workflow or standalone task.

Types of Idempotency in Hatchet

Hatchet supports two different types of idempotency behavior, which you can choose between depending on the needs of your application:

  1. TTL-based idempotency, which says that there can only be one run for a given idempotency key within a specified amount of time after the first run for that key is seen.
  2. Status-based idempotency, which clears the idempotency key when the run that claimed it reaches a terminal status (success, failure, or cancellation). This acts similarly to CANCEL_NEWEST concurrency, where any time there’s a running task holding an idempotency key, any incoming tasks with the same key will be dropped. But once the running task reaches a terminal state, a new one that’s triggered can immediately claim the key once again, with no fixed wait time.

The Idempotency Key Expression

Every idempotency configuration, regardless of strategy, requires an expression, which is a CEL expression that’s evaluated against the input and additional metadata of the run that’s about to be triggered to produce the idempotency key. Two runs with the same computed key are considered duplicates.

⚠️

The idempotency key expression must evaluate to a string.

TTL-based Idempotency

TTL-based idempotency requires two parameters: the expression described above, and a TTL, which determines how long the key should live for. Only one run for a given key will occur in the time window from when the first trigger comes in until the TTL expires.

The TTL window is sliding: each accepted run resets the clock. For instance, if you trigger a workflow at 00:00:00 UTC (midnight) with a TTL of five minutes, and then the same workflow is triggered again with the same inputs and metadata at 00:02:00 UTC, 00:04:00 UTC, and 00:06:00 UTC, only the first one (at midnight) and the final one (at 00:06:00 UTC) will run. After the second run occurs, the lock on the key will be held until 00:11:00 UTC (five minutes after the final run was triggered).

TTL-based idempotency is a good fit when you want to debounce duplicate triggers over a fixed period of time, regardless of how long the run itself takes.

Status-based Idempotency

Status-based idempotency keeps the idempotency key claimed only while the run that owns it is still active. Once that run reaches a terminal status (success, failure, or cancellation), the key is released immediately, and the next trigger with the same key can claim it and start a fresh run with no fixed wait time.

This behaves similarly to CANCEL_NEWEST concurrency: while a run is holding the key, any incoming run with the same key is dropped (raising an idempotency collision), but once the running task finishes, a new one can immediately take its place.

Instead of a TTL, status-based idempotency takes a fallback TTL. Because the key is normally released when the run reaches a terminal state, the fallback TTL exists only as a safety net: it caps the longest the key can remain claimed if, for some reason, the run never reaches a terminal status. You should generally set the fallback TTL comfortably longer than you expect the run to take.

For example, if you trigger a run at 00:00:00 UTC that takes thirty seconds to complete, any duplicate triggers that come in before 00:00:30 UTC will collide and be rejected. But a duplicate that arrives at 00:00:31 UTC, just after the first run has finished, will be accepted and start a new run, because the key was released as soon as the first run reached its terminal status.

Status-based idempotency is a good fit when you want to guarantee that only one run for a given key is in flight at any moment, without imposing a cooldown after it completes.

Handling Collisions

When a collision occurs, the engine will reject the workflow run, and, if the run was triggered from an SDK, then the SDK will raise an exception indicating that there was an idempotency collision. This exception will contain the id of the workflow run that already existed that had claimed the key already, so you can retrieve its output if you like.

In other cases, such as triggering by events, the idempotency collision will be swallowed, and no additional runs will be created, but the event will still be ingested correctly without an error being raised.