createHook

Create a low-level hook to resume workflows with arbitrary payloads.

Creates a hook primitive that can be used to suspend a workflow and later resume it with an arbitrary serializable payload.

Unlike createWebhook(), which always generates a random token for its public HTTP endpoint, createHook() accepts an optional custom token. If you omit token, Workflow generates a unique token automatically. Use a custom token only when the sender can deterministically reconstruct it. Hooks are resumed server-side via resumeHook() and can receive any serializable payload.

import { createHook } from "workflow"

export async function hookWorkflow() {
  "use workflow";
  // `using` automatically disposes the hook when it goes out of scope
  using hook = createHook();  
  const result = await hook; // Suspends the workflow until the hook is resumed
}

API Signature

Parameters

NameTypeDescription
optionsHookOptionsConfiguration options for the hook.

HookOptions

NameTypeDescription
tokenstringUnique token that is used to associate with the hook. When specifying an explicit token, the token should be constructed with information that the dispatching side can reliably reconstruct the token with the information it has available. Deterministic tokens are intended for use with createHook() and server-side resumeHook() only. For webhooks (createWebhook()), tokens are always randomly generated to prevent unauthorized access to the public webhook endpoint. If not provided, a randomly generated token will be assigned.
metadataSerializableAdditional user-defined data to include with the hook payload.
isWebhookbooleanWhether this hook can be resumed via the public webhook endpoint. When true, the hook can be triggered by sending an HTTP request to the public /.well-known/workflow/v1/webhook/{token} URL. This is automatically set when using createWebhook(). When false (the default), the hook can only be resumed server-side via resumeHook().

Returns

Hook<T>

Hook

NameTypeDescription
tokenstringThe token used to identify this hook.
dispose() => voidDisposes the hook, releasing its token for reuse by other workflows. After calling dispose(), the hook will no longer receive any events. This is useful when you want to explicitly release a hook token before the workflow completes, allowing another workflow to register a hook with the same token.

The returned Hook object also implements AsyncIterable<T>, which allows you to iterate over incoming payloads using for await...of syntax.

The isWebhook option in HookOptions controls whether the hook can be resumed via the public webhook endpoint (/.well-known/workflow/v1/webhook/{token}). It defaults to false, meaning hooks created with createHook() can only be resumed server-side via resumeHook(). The createWebhook() function sets this to true automatically.

Examples

Basic Usage

When creating a hook, you can specify a payload type for automatic type safety:

import { createHook } from "workflow"

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

  using hook = createHook<{ approved: boolean; comment: string }>(); 
  console.log("Send approval to token:", hook.token);

  const result = await hook;

  if (result.approved) {
    console.log("Approved with comment:", result.comment);
  }
}

Machine-readable logging

Emit a structured log line when a hook is created so external systems can discover its token programmatically:

import { createHook } from "workflow"

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

  using hook = createHook<{ approved: boolean }>();

  console.info(JSON.stringify({ 
    event: "workflow.hook.created", 
    token: hook.token, 
  })); 

  return await hook;
}

Example log line:

{"event":"workflow.hook.created","token":"nk_abc123"}

Customizing Tokens

By default, Workflow generates a unique token for each hook. You can provide a custom token when the resuming side needs to reconstruct the token without prior communication.

import { createHook } from "workflow";

export async function slackBotWorkflow(channelId: string) {
  "use workflow";

  // Token constructed from channel ID
  using hook = createHook<SlackMessage>({ 
    token: `slack_messages:${channelId}`, 
  }); 

  for await (const message of hook) {
    if (message.text === "/stop") {
      break;
    }
    await processMessage(message);
  }
}

Waiting for Multiple Payloads

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

import { createHook } from "workflow"

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

  using hook = createHook<{ message: string; done?: boolean }>();

  const payloads = [];
  for await (const payload of hook) { 
    payloads.push(payload);

    if (payload.done) break;
  }

  return payloads;
}

Disposing Hooks Early

You can dispose a hook early to release its token for reuse by another workflow. This is useful for handoff patterns where one workflow needs to transfer a hook token to another workflow while still running.

import { createHook } from "workflow"

export async function handoffWorkflow(channelId: string) {
  "use workflow";

  const hook = createHook<{ message: string; handoff?: boolean }>({
    token: `channel:${channelId}`
  });

  for await (const payload of hook) {
    console.log("Received:", payload.message);

    if (payload.handoff) {
      hook.dispose(); // Release the token for another workflow
      break;
    }
  }

  // Continue with other work while another workflow uses the token
}

After calling dispose(), the hook will no longer receive events and its token becomes available for other workflows to use.

Automatic Disposal with using

Hooks implement the TC39 Explicit Resource Management proposal, allowing automatic disposal with the using keyword:

import { createHook } from "workflow"

export async function scopedHookWorkflow(channelId: string) {
  "use workflow";

  {
    using hook = createHook<{ message: string }>({ 
      token: `channel:${channelId}`
    });

    const payload = await hook;
    console.log("Received:", payload.message);
  } // hook is automatically disposed here

  // Token is now available for other workflows to use
  console.log("Hook disposed, continuing with other work...");
}

This is equivalent to manually calling dispose() but ensures the hook is always cleaned up, even if an error occurs.