Skip to content

Commit c84bf7f

Browse files
authored
docs: use the run ID as the idempotency key for external services (#4953)
## Summary Follow-up to [#4952](#4952). The new "Side effects inside `run()`" section told readers to derive the provider's idempotency key from a payload ID, but the more general answer is the run ID: `ctx.run.id` is stable across every attempt of a run, is always available, and is the same value the default `run` scope already mixes into a Trigger.dev idempotency key. The Stripe refund example now keys the provider call on `ctx.run.id`, and a short paragraph explains when a business ID from the payload is the better choice (when the same task can be triggered more than once for the same order and you want deduplication across separate runs). Docs only.
1 parent 7eb53a0 commit c84bf7f

1 file changed

Lines changed: 8 additions & 6 deletions

File tree

‎docs/idempotency.mdx‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ An idempotency key stops a task from being *triggered* twice. It does not stop t
4444

4545
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.
4646

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:
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 every attempt of the run. The run ID, `ctx.run.id`, always qualifies: retries are attempts of the same run, so the ID does not change between them. This is the same value the default `run` scope mixes into a Trigger.dev idempotency key.
4848

4949
```ts /trigger/refund-order.ts
5050
import { idempotencyKeys, task } from "@trigger.dev/sdk";
@@ -56,20 +56,22 @@ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
5656
export const refundOrder = task({
5757
id: "refund-order",
5858
retry: { maxAttempts: 3 },
59-
run: async (payload: { orderId: string; paymentIntentId: string; amount: number }) => {
60-
// Stripe returns the original refund for a repeated key instead of creating a second one
59+
run: async (payload: { orderId: string; paymentIntentId: string; amount: number }, { ctx }) => {
60+
// The run ID is stable across retries, so Stripe returns the original refund instead of creating a second one
6161
await stripe.refunds.create(
6262
{ payment_intent: payload.paymentIntentId, amount: payload.amount },
63-
{ idempotencyKey: `refund-${payload.orderId}` }
63+
{ idempotencyKey: `refund-${ctx.run.id}` }
6464
);
6565

66-
// The Trigger.dev key deduplicates the trigger, so a retry does not create a second email run
67-
const idempotencyKey = await idempotencyKeys.create(`refund-email-${payload.orderId}`);
66+
// The Trigger.dev key is run-scoped by default, so a retry does not create a second email run
67+
const idempotencyKey = await idempotencyKeys.create("refund-email");
6868
await sendRefundEmail.trigger({ orderId: payload.orderId }, { idempotencyKey });
6969
},
7070
});
7171
```
7272

73+
The run ID protects against retries of one run. If the same task can be triggered more than once for the same order, either trigger it with a Trigger.dev idempotency key so repeated triggers resolve to the same run and the same run ID, or key the provider call on a business ID from your payload instead, such as `refund-${payload.orderId}`, which deduplicates across separate runs too.
74+
7375
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.
7476

7577
## `idempotencyKey` option

0 commit comments

Comments
 (0)