---
title: createWebhook
description: Create webhooks to suspend and resume workflows via HTTP requests.
type: reference
summary: Use createWebhook to suspend a workflow until an HTTP request is received at a generated URL.
prerequisites:
  - /docs/foundations/hooks
related:
  - /docs/api-reference/workflow/create-hook
---

Creates a webhook that can be used to suspend and resume a workflow run upon receiving an HTTP request.

Webhooks provide a way for external systems to send HTTP requests directly to your workflow. Unlike hooks which accept arbitrary payloads, webhooks work with standard HTTP `Request` objects and can return HTTP `Response` objects.

<Callout type="warn">
`createWebhook()` creates a public endpoint at `/.well-known/workflow/v1/webhook/:token`, and the token in that URL is the only authorization performed for incoming requests resuming that webhook. This is convenient for prototypes and simple resume links because it avoids creating another route, but if you need stronger security, prefer [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own route and authorize the request before calling [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) to avoid unauthenticated workflow resumptions.
</Callout>

```ts lineNumbers
import { createWebhook } from "workflow"

export async function webhookWorkflow() {
  "use workflow";
  // `using` automatically disposes the webhook when it goes out of scope
  using webhook = createWebhook();  // [!code highlight]
  console.log("Webhook URL:", webhook.url);

  const request = await webhook; // Suspends until HTTP request received
  console.log("Received request:", request.method, request.url);
}
```

## API Signature

### Parameters

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

### Returns

<TSDoc
definition={`
import { createWebhook } from "workflow";
export default createWebhook;`}
showSections={['returns']}
/>

The returned `Webhook` object has:

- `url`: The HTTP endpoint URL that external systems can call
- `token`: The unique token identifying this webhook
- `getConflict()`: A promise that resolves with the conflicting run if another active hook already owns this token, or `null` once the webhook endpoint has been registered
- Implements `AsyncIterable<T>` for handling multiple requests, where `T` is `Request` (default) or `RequestWithResponse` (manual mode)

When using `createWebhook({ respondWith: 'manual' })`, the resolved request type is `RequestWithResponse`, which extends the standard `Request` interface with a `respondWith(response: Response): Promise<void>` method for sending custom responses back to the caller.

<Callout type="info">
Use the simplest option that satisfies the prompt:

- `createWebhook()` — generated callback URL, and the default `202 Accepted` response is fine
- `createWebhook({ respondWith: 'manual' })` — generated callback URL, but you must send a custom body, status, or headers
- `createHook()` + `resumeHook()` — the app resumes from server-side code with a deterministic business token instead of a generated callback URL
</Callout>

<details>
<summary>Common wrong turns</summary>

- Do not use `respondWith: 'manual'` just because the flow has a callback URL.
- Do not use `RequestWithResponse` unless you chose manual mode.
- Do not invent a custom callback route when `webhook.url` is the intended callback surface.
</details>

## Examples

### Basic Usage

Create a webhook that receives HTTP requests and logs the request details:

```typescript lineNumbers
import { createWebhook } from "workflow"

export async function basicWebhookWorkflow() {
  "use workflow";

  using webhook = createWebhook(); // [!code highlight]
  console.log("Send requests to:", webhook.url);

  const request = await webhook;

  console.log("Method:", request.method);
  console.log("Headers:", Object.fromEntries(request.headers));

  const body = await request.text();
  console.log("Body:", body);
}
```

### Responding to Webhook Requests (Manual Mode)

Use this section only when the caller requires a non-default HTTP response. If `202 Accepted` is acceptable, use `createWebhook()` without `respondWith: "manual"`.

Pass `{ respondWith: "manual" }` to get a `RequestWithResponse` object with a `respondWith()` method. Note that `respondWith()` must be called from within a step function:

```typescript lineNumbers
import { createWebhook, type RequestWithResponse } from "workflow"

async function sendResponse(request: RequestWithResponse): Promise<void> {
  "use step";
  await request.respondWith(
    new Response(JSON.stringify({ success: true, message: "Received!" }), {
      status: 200,
      headers: { "Content-Type": "application/json" }
    })
  );
}

export async function respondingWebhookWorkflow() {
  "use workflow";

  using webhook = createWebhook({ respondWith: "manual" });
  console.log("Webhook URL:", webhook.url);

  const request = await webhook;

  // Send a custom response back to the caller
  await sendResponse(request);

  // Continue workflow processing
  const data = await request.json();
  await processData(data);
}

async function processData(data: any): Promise<void> {
  "use step";
  // Process the webhook data
  console.log("Processing:", data);
}
```

### Waiting for Multiple Requests

You can also wait for multiple requests by using the `for await...of` syntax.

```typescript lineNumbers
import { createWebhook, type RequestWithResponse } from "workflow"

async function sendAck(request: RequestWithResponse, message: string) {
  "use step";
  await request.respondWith(
    Response.json({ received: true, message })
  );
}

async function processEvent(data: any) {
  "use step";
  console.log("Processing event:", data);
}

export async function eventCollectorWorkflow() {
  "use workflow";

  using webhook = createWebhook({ respondWith: "manual" });
  console.log("Send events to:", webhook.url);

  for await (const request of webhook) { // [!code highlight]
    const data = await request.json();

    if (data.type === "done") {
      await sendAck(request, "Workflow complete");
      break;
    }

    await sendAck(request, "Event received");
    await processEvent(data);
  }
}
```

## Related Functions

- [`createHook()`](/docs/api-reference/workflow/create-hook) — Use when the app resumes from server-side code with a deterministic business token.
- [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) — Pairs with `createHook()` for deterministic server-side resume.
- [`defineHook()`](/docs/api-reference/workflow/define-hook) — Type-safe hook helper.
- [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) — Low-level runtime API. Most integrations should call `webhook.url` directly instead of adding a custom callback route.
