惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
Jina AI
Jina AI
博客园_首页
WordPress大学
WordPress大学
罗磊的独立博客
小众软件
小众软件
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
爱范儿
爱范儿
The Cloudflare Blog
GbyAI
GbyAI
C
Check Point Blog
腾讯CDC
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
博客园 - 聂微东
IT之家
IT之家
雷峰网
雷峰网
H
Help Net Security
博客园 - 叶小钗
美团技术团队
D
DataBreaches.Net

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors
Idempotency
2026-05-31 · via Workflow SDK Documentation

Ensure external side effects happen exactly once, even when steps are retried or workflows are replayed.

Workflow steps can be retried (on failure) and replayed (on cold start). If a step calls an external API that isn't idempotent, retries could create duplicate charges, send duplicate emails, or double-process records. Use idempotency keys to make these operations safe.

  • Charging a payment (Stripe, PayPal)
  • Sending transactional emails or SMS
  • Creating records in external systems where duplicates are harmful
  • Any step that has side effects in systems you don't control

Every step has a unique, deterministic stepId available via getStepMetadata(). Pass this as the idempotency key to external APIs:

import { getStepMetadata } from "workflow";

declare function createCharge(customerId: string, amount: number): Promise<{ id: string }>; // @setup
declare function sendReceipt(customerId: string, chargeId: string): Promise<void>; // @setup

export async function chargeCustomer(customerId: string, amount: number) {
  "use workflow";

  const charge = await createCharge(customerId, amount);
  await sendReceipt(customerId, charge.id);

  return { customerId, chargeId: charge.id, status: "completed" };
}

Step function with idempotency key

import { getStepMetadata } from "workflow";

async function createCharge(
  customerId: string,
  amount: number
): Promise<{ id: string }> {
  "use step";

  const { stepId } = getStepMetadata(); 

  // Stripe uses the idempotency key to deduplicate requests.
  // If this step is retried, Stripe returns the same charge.
  const charge = await fetch("https://api.stripe.com/v1/charges", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
      "Idempotency-Key": stepId, 
    },
    body: new URLSearchParams({
      amount: String(amount),
      currency: "usd",
      customer: customerId,
    }),
  });

  if (!charge.ok) {
    const error = await charge.json();
    throw new Error(`Charge failed: ${error.message}`);
  }

  return charge.json();
}

async function sendReceipt(customerId: string, chargeId: string): Promise<void> {
  "use step";

  const { stepId } = getStepMetadata();

  await fetch("https://api.example.com/receipts", {
    method: "POST",
    headers: { "Idempotency-Key": stepId },
    body: JSON.stringify({ customerId, chargeId }),
  });
}

Workflow does not currently provide distributed locking or true exactly-once delivery across concurrent runs. If two workflow runs could process the same entity concurrently:

  • Rely on the external API's idempotency (like Stripe's Idempotency-Key) rather than checking a local flag.
  • Don't use check-then-act patterns like "read a flag, then write if not set" -- another run could read the same flag between your read and write.

If your external API doesn't support idempotency keys natively, consider adding a deduplication layer (e.g., a database unique constraint on the operation ID).

  • stepId is deterministic. It's the same value across retries and replays of the same step, making it a reliable idempotency key.
  • Always provide idempotency keys for non-idempotent external calls. Even if you think a step won't be retried, cold-start replay will re-execute it.
  • Handle 409/conflict as success. If an external API returns "already processed," treat that as a successful result, not an error.
  • Make your own APIs idempotent where possible. Accept an idempotency key and return the cached result on duplicate requests.