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

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
美团技术团队
大猫的无限游戏
大猫的无限游戏
H
Help Net Security
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
M
MIT News - Artificial intelligence
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
A
About on SuperTechFans
Recent Announcements
Recent Announcements
D
Docker
Vercel News
Vercel News
Engineering at Meta
Engineering at Meta
腾讯CDC
Martin Fowler
Martin Fowler
阮一峰的网络日志
阮一峰的网络日志

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.