Skip to main content
Hooks provide a powerful mechanism for pausing workflow execution and resuming it later with external data. They enable workflows to wait for external events, user interactions (also known as “human in the loop”), or HTTP requests. This guide will teach you the core concepts, starting with the low-level Hook primitive and building up to the higher-level Webhook abstraction.

Understanding Hooks

At their core, Hooks are a low-level primitive that allows you to pause a workflow and resume it later with arbitrary serializable data. Think of them as suspension points in your workflow where you’re waiting for external input. When you create a hook, it generates a unique token that external systems can use to send data back to your workflow. This makes hooks perfect for scenarios like:
  • Waiting for approval from a user or admin
  • Receiving data from an external system or service
  • Implementing event-driven workflows that react to multiple events over time

Creating Your First Hook

Let’s start with a simple example. Here’s a workflow that creates a hook and waits for external data:
The workflow will pause at await hook until external code sends data to resume it.
We recommend using the using keyword which implements the TC39 Explicit Resource Management proposal for automatic cleanup.
See the full API reference for createHook() for all available options.

Resuming a Hook

To send data to a waiting workflow, use resumeHook() from an API route, server action, or any other external context:
The key points:
  • Hooks allow you to pass any serializable data as the payload
  • You need the hook’s token to resume it
  • The workflow will resume execution right where it left off

Custom Tokens for Deterministic Hooks

By default, hooks generate a random token. However, you often want to use a custom token that external systems can reconstruct. This is especially useful for long-running workflows where the same workflow instance should handle multiple events. For example, imagine a Slack bot where each channel should have its own workflow instance:
Now your Slack webhook handler can deterministically resume the correct workflow:

Receiving Multiple Events

Hooks are reusable - they implement AsyncIterable, which means you can use for await...of to receive multiple events over time:
Each time you call resumeHook() with the same token, the loop receives another value.

Disposing Hooks Early

When a workflow ends, hooks are automatically disposed. However, you may want to release a hook token early so another workflow can use it while your workflow continues running. Use a block scope with using to control when disposal happens:
You can also manually dispose using the dispose() method:
After disposal, the hook will no longer receive events and the async iterator will stop yielding values.

Understanding Webhooks

While hooks are powerful, they require you to manually handle HTTP requests and route them to workflows. Webhooks solve this by providing a higher-level abstraction built on top of hooks that:
  1. Automatically serializes the entire HTTP Request object
  2. Provides an automatically addressable url property pointing to the generated webhook endpoint
  3. Handles sending HTTP Response objects back to the caller
When using Workflow DevKit, webhooks are automatically wired up at /.well-known/workflow/v1/webhook/:token without any additional setup.
See the full API reference for createWebhook() for all available options.

Creating Your First Webhook

Here’s a simple webhook that receives HTTP requests. Like hooks, webhooks support the using keyword for automatic cleanup:
The webhook will automatically respond with a 202 Accepted status by default. External systems can simply make an HTTP request to the webhook.url to resume your workflow.

Sending Custom Responses

Webhooks provide two ways to send custom HTTP responses: static responses and dynamic responses.

Static Responses

Use the respondWith option to provide a static response that will be sent automatically for every request:

Dynamic Responses (Manual Mode)

For dynamic responses based on the request content, set respondWith: "manual" and call the respondWith() method on the request:
When using respondWith: "manual", the respondWith() method must be called from within a step function due to serialization requirements. This requirement may be removed in the future.

Handling Multiple Webhook Requests

Like hooks, webhooks support iteration:

Hooks vs. Webhooks: When to Use Each

Use Hooks when:
  • You need full control over the payload structure
  • You’re integrating with custom event sources
  • You want strong TypeScript typing with defineHook()
Use Webhooks when:
  • You’re receiving HTTP requests from external services
  • You need to send HTTP responses back to the caller
  • You want automatic URL routing without writing API handlers

Advanced Patterns

Type-Safe Hooks with defineHook()

The defineHook() helper provides type safety and runtime validation between creating and resuming hooks using Standard Schema v1. Use any compliant validator like Zod or Valibot:
This pattern is especially valuable in larger applications where the workflow and API code are in separate files, providing both compile-time type safety and runtime validation.

Best Practices

Token Design

When using custom tokens:
  • Make them deterministic: Base them on data the external system can reconstruct (like channel IDs, user IDs, etc.)
  • Use namespacing: Prefix tokens to avoid conflicts (e.g., slack:${channelId}, github:${repoId})
  • Include routing information: Ensure the token contains enough information to identify the correct workflow instance

Response Handling in Webhooks

  • Use static responses (respondWith: Response) for simple acknowledgments
  • Use manual mode (respondWith: "manual") when responses depend on request processing
  • Remember that respondWith() must be called from within a step function

Iterating Over Events

Both hooks and webhooks support iteration, making them perfect for long-running event loops:
This pattern allows a single workflow instance to handle multiple events over time, maintaining state between events.