Chat SDK approval workflows: how to wait safely across deploys and restarts

HAGO··schedule22분
공유

On August 6, 2026, Vercel introduced requestApproval() through Chat SDK's new chat/workflow entry point. It posts an Approve/Deny card to a chat thread, suspends a Workflow SDK run, and resumes that run when someone decides. The wait is durable across deployments and process restarts.

That is useful, but it is not a complete authorization system. Chat SDK verifies the incoming chat event, Workflow SDK preserves suspended execution, and your application still owns approver policy and the credentials used for the eventual operation. This guide separates those boundaries using the Vercel announcement, the Chat SDK approvals documentation, the Workflow SDK Next.js guide, and Chat SDK source at commit e8cc4bc.

Where an approval pauses and resumes

requestApproval() is more than a card component. The user sees a card in Slack, Teams, or another supported platform, but the wait does not live only in a Node.js process. The documented path looks like this:

user or automation
  -> Next.js route handler / bot event handler
      -> Workflow SDK start()
          -> function marked "use workflow"
              -> Chat SDK requestApproval()
                  -> "use step": post approval card
                  -> Workflow SDK createWebhook(): create resume URL
                  -> suspend workflow

approver clicks
  -> chat platform
      -> platform-signed Chat SDK action event
          -> POST to the card callback URL
              -> resume the waiting workflow
                  -> check the approver
                  -> "use step": replace buttons with the outcome
                      -> run the protected operation if approved

When Vercel says the wait survives deploys and restarts, it is describing Workflow SDK's durable execution. It is not an unresolved Promise kept alive in one warm server process. The current implementation creates a webhook, races its asynchronous iterator against an optional durable sleep(), and uses steps to post the card, edit the resolved card, or notify an unauthorized user.

Thread serialization has a prerequisite

The event handler passes a Thread object into the workflow. Chat SDK documents automatic serialization across that boundary, but the app must call chat.registerSingleton() at startup so the thread can be revived later. TypeScript accepting the argument does not prove that runtime registration is correct.

Next.js also needs explicit Workflow SDK integration. The official setup wraps next.config.ts with withWorkflow() so the build handles the "use workflow" and "use step" directives. If the app uses proxy.ts or middleware, its matcher must not intercept .well-known/workflow/. The chat adapter, state adapter, and Workflow SDK storage and queue backend, called a world, must also be configured for the target environment.

After a decision or timeout, the implementation removes the buttons and adds an outcome line. That reduces stale clicks, but it is not a complete audit log. Store the request ID, workflow run ID, immutable target, approver ID, policy version, and operation result elsewhere too.

Check the shipped versions before installing

The npm registry reported chat@4.36.0 as the latest release on August 7, 2026. Its published tarball contains the chat/workflow export plus the JavaScript and TypeScript declarations for requestApproval(). The package requires Node.js 20 or later. An older Vercel human-in-the-loop article still says Node.js 18, so do not use that line as the current package requirement.

chat@4.36.0 declares workflow as an optional peer dependency with the range ^5.0.0-beta.35, while the Workflow SDK site labels v4 as its latest documentation set. The approval API is present in the published package, but check the lockfile and use documentation that matches the installed Workflow SDK version.

Put the authorization policy in the workflow

The following workflow requests permission for a production deployment. It names the allowed platform user IDs and handles timeout separately from denial. The code follows the documented API, but it was not deployed to a real Slack workspace or Vercel project during this research.

// workflows/deploy-production.ts
import { requestApproval } from "chat/workflow";
import type { Thread } from "chat";

type Input = {
  thread: Thread;
  releaseId: string;
  commitSha: string;
  requestedBy: string;
};

export async function deployProduction(input: Input) {
  "use workflow";

  const result = await requestApproval(input.thread, {
    title: `Deploy ${input.releaseId} to production?`,
    description: "Review the immutable commit before deciding.",
    fields: {
      Commit: input.commitSha,
      RequestedBy: input.requestedBy,
      Environment: "production",
    },
    approvers: ["U_RELEASE_ADMIN_1", "U_RELEASE_ADMIN_2"],
    timeout: "30m",
  });

  if (result.timedOut) {
    await recordDecision(input.releaseId, "timed-out");
    return;
  }
  if (!result.approved || !result.user) {
    await recordDecision(input.releaseId, "denied");
    return;
  }

  await runDeployment({
    releaseId: input.releaseId,
    commitSha: input.commitSha,
    approvedBy: result.user.id,
  });
}

async function recordDecision(id: string, state: string) {
  "use step";
  // Write to the audit store using id as the idempotency key.
}

async function runDeployment(input: {
  releaseId: string;
  commitSha: string;
  approvedBy: string;
}) {
  "use step";
  // Call a deployment API that treats releaseId as an idempotency key.
}

A handler starts this with start(deployProduction, [input]). Use an immutable artifact ID rather than a movable branch, and recheck it before execution so the approved and deployed targets cannot drift apart.

Test outcomes, not just card rendering

The repository unit tests cover approval, denial, timeout, clicks from users outside approvers, and malformed payloads. They mock the Workflow SDK primitives in process. That is good coverage of local control flow, but it does not independently prove that a production run resumes after deployment or that a real platform signature is verified.

Add integration tests for the boundaries your app uses. Unauthorized clicks should leave the run waiting, duplicate events must not deploy twice, and a restart must not lose the run. Preserve the distinction between timeout and denial, then test a result-card edit failure after the operation finishes.

Authentication and authorization are separate

The approvals documentation says Chat SDK verifies the platform signature on incoming actions. It therefore treats the result's user.id as the actual person who clicked. It also says the button callback URL does not reach the client directly. Those are meaningful protections, but they have a boundary.

The current parseDecision() function does not perform cryptographic verification by itself. It reads the action type, action ID, and user ID from a payload that has reached the workflow. Trust in that payload depends on the supported chat adapter and its verified event path. A custom adapter or a webhook endpoint you build yourself does not inherit the same guarantee unless you implement and test equivalent verification.

A valid identity may still lack permission

Signature verification answers who clicked. It does not answer whether that person may approve a production change. If approvers is omitted, the documented default allows anyone who can see the card to decide. Vercel recommends setting the list for consequential actions.

Larger organizations may check an on-call role, directory group, or change ticket at decision time. Run that lookup in a step, record the policy version, and prefer stable platform IDs over display names.

The final operation needs least privilege and idempotency

A polished approval card does little good if the bot holds unrestricted cloud administrator credentials. Give the workflow service account access only to the project and operation it needs. Do not place secrets in the card description or fields. Show an immutable summary in chat and link to a protected change-management view for sensitive details.

Replacing the buttons does not provide exactly-once deployment. Use an idempotency key such as releaseId, query existing state, and recover when either the card edit or deployment succeeds alone.

Measure each stage instead of guessing at performance

Vercel says this API can remove a custom approval table, an onAction handler, and polling. The announcement gives no latency or cost benchmark. Durable suspension is different from occupying a server process with an in-memory wait, but storage, queue, webhook, and chat API work still have costs. Fewer lines of glue code do not prove a faster approval.

Capture timestamps for these events:

the request handler starts the workflow;

the card-posting step begins and the chat API responds;

the card becomes visible in the channel;

the user clicks and the platform event arrives;

Workflow SDK resumes the run;

the authorization policy completes;

the protected step starts, receives its first external response, and completes;

the card edit and durable audit write complete.

Compare a no-op workflow, card post/edit, the full approval path, and the real deployment. Separate first and warm runs. Fix the region, platform, world, lockfile, and adapter versions. Report human decision time separately.

Durability deserves its own acceptance test rather than being inferred from an ordinary successful run. Post the card, redeploy the application, approve after a controlled delay, and verify that one workflow ID performs the protected operation once. Until that test runs, "survives restarts" is an upstream architectural guarantee, not an observation from your project.

What is established and what remains conditional

The shipped chat@4.36.0 package contains chat/workflow. Its implementation posts an approval card, waits on a Workflow SDK webhook or timeout, rejects users outside an optional allowlist, and edits the card with the outcome. Node.js 20 or later and a Workflow 5 beta peer version are current package constraints.

Successful recovery after a deploy is conditional on correct Workflow SDK and world configuration, Chat singleton registration, and framework integration. Identity trust depends on a supported adapter's verified event path. No performance gain was measured in the announcement, so project-specific instrumentation is still required.

Start in a development channel with an immutable artifact and explicit approver IDs. Exercise unauthorized clicks, duplicate events, timeout, redeployment, and card-edit failure. Then connect a least-privileged service account and an idempotent operation API. The approval card makes the interaction cleaner. The surrounding boundaries make it safe.

Official references

공유

댓글 (0)

첫 댓글을 남겨주세요.