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.

Vendor-official adapter maintained by Dial, not Vercel or Chat SDK contributors. For feature requests, bug reports, and support, file an issue on the adapter's repo.

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

FeatureSupported
Post message
Edit message
Delete message
File uploadsMMS via public URLs
Streaming
Scheduled messages

Rich content

FeatureSupported
Card format
Buttons
Link buttons
Select menus
TablesFlattened to ASCII code blocks
Fields
Images in cards
Modals

Conversations

FeatureSupported
Slash commands
MentionsDMs only — every phone pair is 1:1
Add reactions
Remove reactions
Typing indicator
DMs
Ephemeral messages
User lookup
Parent subject
Native client
Custom API endpointapiBaseUrl override

Message history

FeatureSupported
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-memory

For production, use a persistent state adapter such as @chat-adapter/state-redis instead of in-memory state.

Quick start

lib/bot.ts
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

DirectionChannelTextMedia
InboundSMS
InboundMMS✅ (image / video / audio / file)
InboundiMessage
InboundVoice call✅ (as transcript)
OutboundSMS / 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/sdk Node 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

On this page