This example demonstrates the "Signal + timer" pattern using Workflow DevKit. It shows how to implement human-in-the-loop approval workflows with deterministic hook tokens and timeout behavior.
- Deterministic Hook Tokens: Hook tokens are derived from order IDs, allowing external systems to construct them
- Promise.race Timeout Pattern: Combines hook awaiting with
sleep()for configurable timeout behavior - Type-safe Payloads: Uses
defineHook<T>()for typed approval payloads - External Resume: Workflows can be resumed from any API route using the hook token
-
Install dependencies:
pnpm install -
Start the development server:
pnpm dev -
Start an approval workflow:
curl -X POST http://localhost:3000/api/start \ -H "Content-Type: application/json" \ -d '{"orderId": "order_123", "timeout": "30s"}' -
Approve or reject before timeout:
# Approve curl -X POST http://localhost:3000/api/approve \ -H "Content-Type: application/json" \ -d '{"token": "order_approval:order_123", "approved": true, "comment": "Approved!", "approvedBy": "manager@example.com"}' # Or reject curl -X POST http://localhost:3000/api/approve \ -H "Content-Type: application/json" \ -d '{"token": "order_approval:order_123", "approved": false, "comment": "Rejected - out of stock"}'
Starts a new approval workflow for an order.
Request Body:
{
"orderId": "order_123",
"timeout": "30s"
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
orderId |
string | Yes | - | Unique identifier for the order |
timeout |
string | No | "30s" |
Duration to wait before timeout (e.g., "30s", "5m", "24h") |
Response:
{
"message": "Approval workflow started",
"runId": "wfr_abc123",
"orderId": "order_123",
"timeout": "30s",
"approvalToken": "order_approval:order_123"
}Resumes a waiting workflow with an approval decision.
Request Body:
{
"token": "order_approval:order_123",
"approved": true,
"comment": "Looks good!",
"approvedBy": "manager@example.com"
}| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | The hook token (format: order_approval:{orderId}) |
approved |
boolean | Yes | true to approve, false to reject |
comment |
string | No | Optional comment or reason |
approvedBy |
string | No | Email or identifier of the approver |
Response:
{
"success": true,
"message": "Order approved",
"runId": "wfr_abc123",
"token": "order_approval:order_123",
"approved": true,
"comment": "Looks good!"
}The workflow has three possible outcomes:
- Approved: The order is approved and fulfilled
- Rejected: The order is rejected and cancelled
- Timeout: No decision was made within the timeout period, order is cancelled
// Token is derived from orderId, allowing external systems to construct it
const hook = orderApprovalHook.create({
token: `order_approval:${orderId}`,
});const result = await Promise.race([
hook.then((payload) => ({ type: "approval", payload })),
sleep(timeout).then(() => ({ type: "timeout", payload: null })),
]);import { defineHook } from "workflow";
interface ApprovalPayload {
approved: boolean;
comment?: string;
approvedBy?: string;
}
export const orderApprovalHook = defineHook<ApprovalPayload>();- Order approvals in e-commerce
- Expense report approvals
- Document review workflows
- Multi-step approval chains
- Any human-in-the-loop process with timeouts