Dial
Give your Chat SDK bot a phone number — SMS, MMS, iMessage in and out, plus inbound voice-call transcripts, with HMAC-signed webhooks and no channel-specific handlers.
The Dial adapter connects Chat SDK bots to a real phone number — SMS, MMS, iMessage, and inbound voice-call transcripts, all through one adapter. The same onNewMention handler that already answers Slack, Teams, or Discord messages fires for phone traffic hitting your Dial number, and thread.post replies go back out over those channels through the official @getdial/sdk.
The adapter maps a phone conversation to a Chat SDK thread (identified by the pair of phone numbers), an SMS/MMS/iMessage to a message with optional media attachments, and a completed voice call's transcript to a message on that same thread — so subscriptions, handlers, posts, and per-thread state work the same as with any other adapter.
Feature support
Messaging
| Feature | Supported |
|---|---|
| Post message | |
| Edit message | |
| Delete message | |
| File uploads | MMS via public URLs |
| Streaming | |
| Scheduled messages |
Rich content
| Feature | Supported |
|---|---|
| Card format | |
| Buttons | |
| Link buttons | |
| Select menus | |
| Tables | Flattened to ASCII code blocks |
| Fields | |
| Images in cards | |
| Modals |
Conversations
| Feature | Supported |
|---|---|
| Slash commands | |
| Mentions | DMs only — every phone pair is 1:1 |
| Add reactions | |
| Remove reactions | |
| Typing indicator | |
| DMs | |
| Ephemeral messages | |
| User lookup | |
| Parent subject | |
| Native client | |
| Custom API endpoint | apiBaseUrl override |
Message history
| Feature | Supported |
|---|---|
| Fetch messages | |
| Fetch single message | |
| Fetch thread info | |
| Fetch channel messages | |
| List threads | |
| Fetch channel info | |
| Post channel message |
Install
pnpm add @getdial/chat-sdk-adapter chat @chat-adapter/state-memoryFor production, use a persistent state adapter such as @chat-adapter/state-redis instead of in-memory state.
Quick start
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import { createDialAdapter } from "@getdial/chat-sdk-adapter";
export const bot = new Chat({
userName: "mybot",
state: createMemoryState(),
adapters: {
dial: createDialAdapter({
apiKey: process.env.DIAL_API_KEY,
fromNumberId: process.env.DIAL_FROM_NUMBER_ID,
webhookSecret: process.env.DIAL_WEBHOOK_SECRET,
}),
},
});
bot.onNewMention(async (thread, message) => {
await thread.post(`heard you: ${message.text}`);
});Bind the webhook on whichever HTTP framework you use — every framework Chat SDK works on works here:
// e.g. Next.js route handler
export async function POST(req: Request) {
return bot.webhooks.dial(req);
}Configuration
Prop
Type
Webhook setup
Point a Dial webhook subscription at the endpoint you handed to bot.webhooks.dial. Create the subscription from the Dial dashboard's Webhooks page or over the API:
curl -X POST https://api.getdial.ai/api/v1/webhooks \
-H "Authorization: Bearer $DIAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "targetUrl": "https://your-bot.example.com/webhook/dial", "eventTypes": ["*"] }'The whsec_… secret returned at creation goes in DIAL_WEBHOOK_SECRET. See the Dial Webhooks reference for signature format, retries, and delivery semantics.
Signature verification
When webhookSecret is set, every request must carry:
X-Dial-Signature: t=<unix_seconds>,v1=<hex-hmac-sha256(secret, `${t}.${rawBody}`)>The adapter recomputes the HMAC with Node's crypto.createHmac + timingSafeEqual, rejects timestamps older than 5 minutes (replay protection), and answers 401 on any mismatch.
What the adapter carries
| Direction | Channel | Text | Media |
|---|---|---|---|
| Inbound | SMS | ✅ | — |
| Inbound | MMS | ✅ | ✅ (image / video / audio / file) |
| Inbound | iMessage | ✅ | ✅ |
| Inbound | Voice call | ✅ (as transcript) | — |
| Outbound | SMS / MMS / iMessage | ✅ | ✅ (via attachment URLs) |
Voice calls surface as their transcript via the call.transcribed event — the adapter fetches the transcript through @getdial/sdk.getCall() and forwards it as a message on the caller's thread.
Threads
A Chat SDK thread here is a pair of phone numbers — your Dial-owned number and the peer's — encoded as:
dial:{yourDialNumber}:{peerNumber}Every distinct pair is a distinct thread. Chat SDK's per-thread state (subscriptions, locks, conversation memory) is scoped per-pair, so multiple concurrent conversations don't leak into each other.
Design notes
- Outbound sends and transcript fetches go through the official
@getdial/sdkNode SDK — no hand-rolled HTTP. - Signature verification uses Node's stdlib
crypto— HMAC-SHA256 + constant-time compare — matching the exact primitive Dial's server signs with. - ESM-only, TypeScript-first. Requires Node 18+.
- Peer-depends on
chat ^4.20.0.
Learn more
- Adapter source + issues:
GetDial-AI/chat-sdk-adapter - Dial docs (this adapter): docs.getdial.ai/integrations/agent-clients/vercel-chat-sdk
- Dial platform docs: docs.getdial.ai
- Node SDK the adapter wraps:
@getdial/sdk