resumeHook
Resume a paused workflow by sending a payload to a hook token.
Resumes a workflow run by sending a payload to a hook identified by its token.
It creates a hook_received event and re-triggers the workflow to continue execution.
resumeHook is a runtime function that must be called from outside a workflow function.
import { resumeHook } from "workflow/api";
export async function POST(request: Request) {
const { token, data } = await request.json();
try {
const result = await resumeHook(token, data);
return Response.json({
runId: result.runId
});
} catch (error) {
return new Response("Hook not found", { status: 404 });
}
}API signature
Parameters
| Name | Type | Description |
|---|---|---|
tokenOrHook | string | { runId: string; hookId: string; token: string; ownerId: string; projectId: string; environment: string; createdAt: Date; metadata?: unknown; specVersion?: number | undefined; isWebhook?: boolean | undefined; isSystem?: boolean | undefined; tokenRetentionUntil?: Date | undefined; resumeContext?: { ...; } | ... | The unique token identifying the hook, or the hook object itself |
payload | NonNullable<T> | The data payload to send to the hook |
encryptionKeyOverride | PayloadKey |
Returns
Returns a Promise<Hook> that resolves to:
| Name | Type | Description |
|---|---|---|
runId | string | |
hookId | string | |
token | string | |
ownerId | string | |
projectId | string | |
environment | string | |
createdAt | Date | |
metadata | unknown | |
specVersion | number | undefined | |
isWebhook | boolean | undefined | |
isSystem | boolean | undefined | |
tokenRetentionUntil | Date | undefined | |
resumeContext | { deploymentId: string; workflowName: string; runSpecVersion?: number | undefined; workflowCoreVersion?: string | undefined; traceCarrier?: Record<string, string> | undefined; encryptionPublicKey?: string | undefined; hookResumeInputVersion?: number | undefined; } | undefined | |
resumeCapabilities | { hookResumeDedupVersion: number; } | undefined |
Examples
Basic API route
Using resumeHook in a basic API route to resume a hook:
import { resumeHook } from "workflow/api";
export async function POST(request: Request) {
const { token, data } = await request.json();
try {
const result = await resumeHook(token, data);
return Response.json({
success: true,
runId: result.runId
});
} catch (error) {
return new Response("Hook not found", { status: 404 });
}
}With type safety
Defining a payload type and using resumeHook to resume a hook with type safety:
import { resumeHook } from "workflow/api";
type ApprovalPayload = {
approved: boolean;
comment: string;
};
export async function POST(request: Request) {
const { token, approved, comment } = await request.json();
try {
const result = await resumeHook<ApprovalPayload>(token, {
approved,
comment,
});
return Response.json({ runId: result.runId });
} catch (error) {
return Response.json({ error: "Invalid token" }, { status: 404 });
}
}Server action (Next.js)
Using resumeHook in Next.js server actions to resume a hook:
"use server";
import { resumeHook } from "workflow/api";
export async function approveRequest(token: string, approved: boolean) {
try {
const result = await resumeHook(token, { approved });
return result.runId;
} catch (error) {
throw new Error("Invalid approval token");
}
}Webhook handler
Using resumeHook in a generic webhook handler to resume a hook:
import { resumeHook } from "workflow/api";
// Generic webhook handler that forwards data to a hook
export async function POST(request: Request) {
const url = new URL(request.url);
const token = url.searchParams.get("token");
if (!token) {
return Response.json({ error: "Missing token" }, { status: 400 });
}
try {
const body = await request.json();
const result = await resumeHook(token, body);
return Response.json({ success: true, runId: result.runId });
} catch (error) {
return Response.json({ error: "Hook not found" }, { status: 404 });
}
}Resume or start
A common endpoint shape is "resume or start": one route that resumes the active workflow run for a business key if one exists, or starts a new run otherwise. This comes up when the workflow uses a deterministic hook token as its idempotency key, for example, one active run per order or conversation.
resumeHook() is the resume half of that flow. Try it first; if it throws HookNotFoundError, no active run owns the token yet, so start the workflow. One subtlety: start() returns before the new run executes and registers its hook, so you cannot resume immediately after starting. Retry the resume until the hook is registered: if you drop the payload and only start the workflow, the data from this request is lost.
import { resumeHook, start } from "workflow/api";
import { HookNotFoundError } from "workflow/errors";
import { processOrder } from "./workflows/process-order";
type OrderRequest = { confirmed: boolean };
async function resumeWithRetry(token: string, payload: OrderRequest) {
for (let attempt = 0; attempt < 5; attempt++) {
try {
return await resumeHook(token, payload);
} catch (error) {
if (!HookNotFoundError.is(error)) throw error;
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
throw new Error("Workflow did not register its hook in time");
}
export async function POST(request: Request) {
const { orderId, confirmed } = await request.json();
const token = `order:${orderId}`;
const payload = { confirmed };
try {
// An active run already owns this token: resume it.
const hook = await resumeHook(token, payload);
return Response.json({ runId: hook.runId, reused: true });
} catch (error) {
if (!HookNotFoundError.is(error)) throw error;
}
// No hook yet: start a new run, then retry the resume so this
// request's payload still reaches the workflow.
const run = await start(processOrder, [orderId]);
const resumed = await resumeWithRetry(token, payload);
// A concurrent request can win the race between `start()` and hook
// registration; the resume always reaches the actual active owner.
return Response.json({
runId: resumed.runId,
reused: resumed.runId !== run.runId,
});
}See Run idempotency for the full pattern, including how the workflow claims the token with hook.getConflict() and how concurrent starts converge on one active owner.
Related functions
getHookByToken(): Get hook details before resuming.createHook(): Create a hook in a workflow.defineHook(): Type-safe hook helper.- Idempotency: Deduplicate step side effects and workflow starts.