A customer taps “Create order” twice because the page is slow. If your endpoint starts two workflows, it may send two emails, create two invoices, or even trigger two charges. On November 4, 2026, Vercel announced atomic starts in Workflow SDK: a business key is claimed while the run is admitted, so the second request can identify the first. This is a useful change for automations exposed to retries, webhooks, and double taps.
The race it removes
The older pattern called getHookByToken() and then start(). Two simultaneous requests could both observe “nothing exists” before either started a run. In Vercel's announcement, start() now admits the run and claims its Hook in one atomic operation. A second call with the same token throws HookConflictError; conflictingRunId identifies the owner. The official guide says to use workflow 5.4.0 or later and a compatible World.
Choose the key from your actual business operation: order:123, invoice:abc, or import:2026-11-05. Generating a fresh token for every retry defeats the protection. Using only a user ID is also wrong: it would conflate two legitimate orders placed by the same person.

The copyable core for a Next.js route
In a project already using Workflow SDK, the official example reduces to this pattern. processOrder is your existing workflow; validate and authorize orderId for the caller before accepting a real order. A duplicate response returns the original run ID without starting another one:
import { start } from "workflow/api";
import { HookConflictError } from "workflow/errors";
import { processOrder } from "./workflows/process-order";
export async function POST(request: Request) {
const { orderId } = await request.json();
if (typeof orderId !== "string" || !orderId) {
return Response.json({ error: "orderId required" }, { status: 400 });
}
try {
const run = await start(processOrder, [orderId], {
experimental_hook: { token: `order:${orderId}` },
});
return Response.json({ runId: run.runId, duplicate: false });
} catch (error) {
if (!HookConflictError.is(error)) throw error;
return Response.json({
runId: error.conflictingRunId,
duplicate: true,
});
}
}
To test it, send two concurrent requests with the same test order ID. You should receive one runId: one response with duplicate:false and one with duplicate:true. Send a different ID and confirm it creates another run. An HTTP response does not prove the business effect by itself; inspect the runs and the database or external service where the action occurred.
The critical limit: one run does not guarantee one charge
This protects against duplicate starts. If a step inside the same run charges a provider and loses the confirmation, that step may retry. The guide separates run idempotency from step idempotency: use a stable key such as stepId with external APIs that support it, plus a uniqueness constraint in your database for irreversible operations. Do not treat the Hook as a replacement for those protections.
There is also a time limit. Without retention, the token is released when the run ends, so a late retry could start another run. If your business needs to reject duplicates for days, set experimental_minRetention for the required period and verify World support. If a later request contains genuinely new information the owner should receive, the docs describe resumeHook; do not blindly forward the same payment payload again.
Test status: the pattern and limitations come from official documentation. We checked the code against that API, but did not start two live runs in a Vercel account for this article. Before production use, test concurrency, retention, provider retries, and one low-risk business operation.
