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: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, useresumeHook() from an API route, server action, or any other external context:
- Hooks allow you to pass any serializable data as the payload
- You need the hook’s
tokento 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:Receiving Multiple Events
Hooks are reusable - they implementAsyncIterable, which means you can use for await...of to receive multiple events over time:
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 withusing to control when disposal happens:
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:- Automatically serializes the entire HTTP
Requestobject - Provides an automatically addressable
urlproperty pointing to the generated webhook endpoint - Handles sending HTTP
Responseobjects back to the caller
/.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 theusing keyword for automatic cleanup:
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 therespondWith 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, setrespondWith: "manual" and call the respondWith() method on the request:
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()
- 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:
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:Related Documentation
- Serialization - Understanding what data can be passed through hooks
createHook()API ReferencecreateWebhook()API ReferencedefineHook()API ReferenceresumeHook()API ReferenceresumeHook()API Reference