workflow
Version:
Workflow SDK - Build durable, resilient, and observable workflows
191 lines (140 loc) • 7.07 kB
text/mdx
---
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. Anyone who has the URL can resume the workflow with a request of their choosing, so verify requests before acting on them or use [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own authorized route. See [Hook and webhook security](/docs/foundations/hooks#security).
</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. The endpoint is registered alongside the steps the workflow starts at the same time, not ahead of them, so await `getConflict()` before a step that hands `url` to a caller who may request it right away. See [Registering a hook before a step uses it](/docs/api-reference/workflow/create-hook#registering-a-hook-before-a-step-uses-it).
- 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>
- A callback URL alone does not require `respondWith: 'manual'`.
- 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. Call `respondWith()` 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.