---
title: resumeHook
description: Resume a paused workflow by sending a payload to a hook token.
type: reference
summary: Use resumeHook to send a payload to a hook token and resume a paused workflow.
prerequisites:
  - /docs/foundations/hooks
related:
  - /docs/api-reference/workflow-api/resume-webhook
  - /docs/foundations/idempotency
---

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.

<Callout type="warn">
`resumeHook` is a runtime function that must be called from outside a workflow function.
</Callout>

```typescript lineNumbers
import { resumeHook } from "workflow/api";

export async function POST(request: Request) {
  const { token, data } = await request.json();

  try {
    const result = await resumeHook(token, data); // [!code highlight]
    return Response.json({
      runId: result.runId
    });
  } catch (error) {
    return new Response("Hook not found", { status: 404 });
  }
}
```

## API Signature

### Parameters

<TSDoc
definition={`
import { resumeHook } from "workflow/api";
export default resumeHook;`}
showSections={["parameters"]}
/>

### Returns

Returns a `Promise<Hook>` that resolves to:

<TSDoc
definition={`
import type { Hook } from "@workflow/world";
export default Hook;`}
showSections={["returns"]}
/>

## Examples

### Basic API Route

Using `resumeHook` in a basic API route to resume a hook:

```typescript lineNumbers
import { resumeHook } from "workflow/api";

export async function POST(request: Request) {
  const { token, data } = await request.json();

  try {
    const result = await resumeHook(token, data); // [!code highlight]

    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:

```typescript lineNumbers
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, { // [!code highlight]
      approved, // [!code highlight]
      comment, // [!code highlight]
    }); // [!code highlight]

    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:

```typescript lineNumbers
"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:

```typescript lineNumbers
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.

```typescript lineNumbers
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); // [!code highlight]
    } 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); // [!code highlight]
    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]); // [!code highlight]
  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](/docs/foundations/idempotency#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()`](/docs/api-reference/workflow-api/get-hook-by-token) - Get hook details before resuming.
- [`createHook()`](/docs/api-reference/workflow/create-hook) - Create a hook in a workflow.
- [`defineHook()`](/docs/api-reference/workflow/define-hook) - Type-safe hook helper.
- [Idempotency](/docs/foundations/idempotency) - Deduplicate step side effects and workflow starts.
