Chat SDK Adapter

Optional channel adapter registered in new Hitl({ adapters }) on top of the built-in web inbox. When a workflow calls waitForHuman with a Chat SDK channel, Hitl posts a native approval card (Block Kit, Adaptive Cards, embeds + modal) to Slack, Teams, Discord, or other Chat SDK platforms.

Use both channels together: inbox for internal tools, Chat SDK for where your team already works. Resolution still flows through hitl.inbox on the server; the adapter wires Chat SDK interactivity into that API.

Install

Install the Hitl adapter, the Chat SDK core, and one @chat-adapter/* package per platform you use.

terminal
npm i @hitl-sdk/adapter-chat-sdk chat @chat-adapter/slack

Install @chat-adapter/* for each platform. See the Chat SDK packages list.

You also need Hitl state, a workflow resolver, and Chat SDK state for the bot (often Redis). See State and Workflow Engines.

Set up the Hitl server with chat adapters

Create a Chat SDK bot with platform adapters, then register createChatSdkAdapter in new Hitl({ adapters }). The inbox callback is lazy because new Hitl() constructs the inbox after adapters are registered.

Create lib/hitl.ts and paste the following. Replace createSlackAdapter() and createRedisState() with your platforms and Chat SDK state backend.

lib/hitl.ts
import { Hitl } from "@hitl-sdk/hitl";
import { createChatSdkAdapter } from "@hitl-sdk/adapter-chat-sdk";
import { workflowResolver } from "@hitl-sdk/resolver-workflow-sdk";
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";

const bot = new Chat({
  adapters: { slack: createSlackAdapter() },
  state: createRedisState(), // any Chat SDK state adapter
});

export const hitl = new Hitl({
  state,
  resolver: workflowResolver(),
  adapters: [
    createChatSdkAdapter({
      id: "approvals",
      bot,
      defaultChannel: "slack:C123",
      inbox: () => hitl.inbox, // lazy: Hitl constructs inbox after adapters
    }),
  ],
});

export { bot };

Set defaultChannel to the Slack channel ref your bot should post into when workflows pass channel: "approvals" without a destination suffix.

Register Chat SDK webhooks

Chat SDK owns webhook signature verification and payload parsing. This step is required for Slack/Teams/Discord buttons and modals to reach Hitl. Without mounted webhooks, cards render but clicks go nowhere.

Mount the platform webhook on your HTTP server. On plain Node.js, add a route alongside /.well-known/hitl/v1:

server.ts
import { createServer } from "node:http";
import { hitl, bot } from "./lib/hitl";

createServer((req, res) => {
  if (req.method === "POST" && req.url === "/webhooks/slack") {
    void bot.webhooks.slack(req, res);
    return;
  }

  if (req.method === "POST" && req.url?.startsWith("/.well-known/hitl/v1")) {
    void hitl.handler(req, res);
    return;
  }

  res.statusCode = 404;
  res.end();
}).listen(3000);

Point your Slack app's interactivity URL at https://your-host/webhooks/slack. Add routes for each platform adapter you enable.

Using Next.js, Express, Hono, or Fastify? See Host integration for mount patterns on each framework.

Route to channels from workflows

Pass a channel key on waitForHuman to deliver outside the web inbox. The adapter id (approvals) comes from createChatSdkAdapter({ id: "approvals", ... }).

Request approval in Slack before sending email:

const emailDraft = { to: input.email, subject: input.subject, body: input.body };

// Default channel for adapter id "approvals":
await waitForHuman({
  channel: "approvals",
  message: `Send email to: ${input.email}?`,
  actions: actions().approve().deny().build(),
});

// Specific Slack channel:
await waitForHuman({
  channel: "approvals:slack:C456",
  message: `Send email to: ${input.email}?`,
  actions: actions().approve().deny().build(),
});

// Thread (everything after adapter id is the Chat SDK destination):
await waitForHuman({
  channel: "approvals:slack:C123:1710000000.123456",
  message: `Send email to: ${input.email}?`,
  actions: actions().approve().deny().build(),
});

Escalation uses the same routing key:

reminders: [escalate.to("approvals:slack:C999").after("1h", { mode: "redeliver" })],

How it works

createChatSdkAdapter registers approve/deny handlers on the Chat SDK bot. When a reviewer clicks Submit or Deny, the webhook verifies the payload, parses the interaction, and calls hitl.inbox.resolve with the same API as the web inbox. Hitl updates state and resumes the workflow through your resolver.

Multiple adapters can share one bot; handlers register only once.