createHook

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

Creates a low-level hook primitive that can be used to resume a workflow run with arbitrary payloads.

Hooks allow external systems to send data to a paused workflow without the HTTP-specific constraints of webhooks. They're identified by a token 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. If not provided, a randomly generated token will be assigned.
metadataSerializableAdditional user-defined data to include with the hook payload.

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.

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);
  }
}

Customizing Tokens

Tokens are used to identify a specific hook. You can customize the token to be more specific to a use case.

import { createHook } from "workflow";

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

  // Token constructed from channel ID
  using hook = createHook<SlackMessage>({ 
    token: `slack_webhook:${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.