You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: idempotency keys deduplicate triggers, not side effects inside run() (#4952)
## Summary
Idempotency keys deduplicate task triggers (`trigger()`,
`triggerAndWait()`, `batchTrigger()`). They do not make the code inside
a task's `run()` idempotent: when a run retries, `run()` executes again
from the top, and any API call inside it happens again unless that call
is itself idempotent.
The idempotency page listed "avoiding double-charging customers" as a
use case, but every example on the page only ever deduplicated a child
trigger, so a reader could reasonably assume a payment call made
directly inside a retryable `run()` was protected. It is not, and no
Trigger.dev-side mechanism can protect it: only the payment provider can
deduplicate its own API call, so the provider's idempotency key has to
be passed too.
This is a docs-only change. No SDK changes.
## What changed
- The intro now states the boundary up front: the key applies to the
trigger call and nothing else.
- The use-case bullets say "trigger the X task once" rather than
implying the side effect itself is deduplicated, and the payments bullet
points at the new section.
- A new "Side effects inside `run()`" section explains why moving the
call into a child task does not close the gap either, and shows a Stripe
refund passing Stripe's `idempotencyKey` alongside a Trigger.dev key for
the follow-up email trigger.
Addresses the documentation side of
[#4627](#4627).
Supersedes
[#4650](#4650), which
was auto-closed by the vouch gate.
Copy file name to clipboardExpand all lines: docs/idempotency.mdx
+41-5Lines changed: 41 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,9 @@ title: "Idempotency"
3
3
description: "An API call or operation is idempotent if it has the same result when called more than once."
4
4
---
5
5
6
-
We currently support idempotency at the task level, meaning that if you trigger a task with the same `idempotencyKey` twice, the second request will not create a new task run. Instead, the original run's handle is returned, allowing you to track the existing run's progress.
6
+
**Idempotency keys deduplicate task triggers.** If you trigger a task with the same `idempotencyKey` twice, the second request does not create a new run. It returns the original run's handle, so you can track the existing run's progress.
7
+
8
+
The key applies to the trigger call and nothing else. It deduplicates `trigger()`, `triggerAndWait()`, and `batchTrigger()`; it does not make the code inside a task's `run()` function idempotent. See [Side effects inside `run()`](#side-effects-inside-run) for what that means when a task calls an external API.
7
9
8
10
## Why use idempotency keys?
9
11
@@ -31,10 +33,44 @@ sequenceDiagram
31
33
32
34
Other common use cases include:
33
35
34
-
-**Preventing duplicate emails** - Ensure a confirmation email is only sent once, even if the parent task retries
35
-
-**Avoiding double-charging customers** - Prevent duplicate payment processing during retries
36
-
-**One-time setup tasks** - Ensure initialization or migration tasks only run once
37
-
-**Deduplicating webhook processing** - Handle the same webhook event only once, even if it's delivered multiple times
36
+
-**Preventing duplicate emails** - Trigger the email task once, even if the parent task retries
37
+
-**Avoiding double-charging customers** - Trigger the payment task once, even if the parent task retries. The payment API call itself still needs the provider's own idempotency key, see [Side effects inside `run()`](#side-effects-inside-run)
38
+
-**One-time setup tasks** - Trigger initialization or migration tasks once
39
+
-**Deduplicating webhook processing** - Trigger the handler task once per webhook event, even if the event is delivered multiple times
40
+
41
+
## Side effects inside `run()`
42
+
43
+
An idempotency key stops a task from being *triggered* twice. It does not stop the code inside that task from *running* twice. When a run retries, `run()` executes again from the top, and every API call, database write, or email inside it happens again unless that operation is itself idempotent.
44
+
45
+
This matters most for payments. A refund issued directly inside a retryable task is issued again on every retry, even when the retry is caused by an unrelated failure later in the same function. Moving the refund into a child task and triggering it with an idempotency key does not close the gap either: the child is triggered once, but its own `run()` can retry after the provider has accepted the request. Only the provider can deduplicate its own API call.
46
+
47
+
Pass the provider's idempotency key on every call that must happen at most once. Derive it from a value that stays the same across retries, such as an ID from your payload:
Use a Trigger.dev idempotency key to stop a retry triggering a task twice, and the provider's idempotency key to stop a retry calling the provider twice. For a side effect with no idempotency support, record that you performed it in your own database and check that record before performing it again.
0 commit comments