Quick start
Backend middleware, one React hook, and your agents are making calls.
1. Install
Backendnpm install @dialer/node
Frontend
npm install @dialer/react
2. Add the middleware
The middleware mints short-lived agent tokens. It sits on any route your frontend can reach.
server.jsimport { DialerAuth } from '@dialer/node' app.use(DialerAuth({ apiKey: process.env.DIALER_API_KEY, getAgentId: (req) => req.user.id }))
This creates a POST /dialer-auth endpoint that the React SDK calls automatically.
3. Render the agent UI
Agent.jsximport { useDialer, CallCard } from '@dialer/react' export default function Agent() { const { activeCall, endCall, status } = useDialer({ authUrl: '/dialer-auth' }) if (!activeCall) { return <div>Waiting for a call...</div> } return <CallCard call={activeCall} onEnd={endCall} /> }
Start a campaign from the dashboard. When a contact answers, the call appears in your agent UI.
Authentication
Two token types, two use cases.
| Token | Format | Use |
|---|---|---|
| API key | sk_live_... |
Server-side. Campaign management, data access, minting agent tokens. Never expose to browsers. |
| Agent token | at_... |
Client-side. Short-lived JWT for the React SDK. Minted by your backend via the middleware or POST /api/auth/token. |
All API requests use the Authorization: Bearer <token> header. Rate limit: 100 req/min.
Backend setup
The @dialer/node middleware handles agent token minting. It's the only backend code you write.
DialerAuth options
| Option | Type | Description |
|---|---|---|
| apiKey* | string | Your sk_live_... API key |
| getAgentId* | (req) => string | Return the current user's member ID from your auth system |
| path | string | Endpoint path. Default: /dialer-auth |
| expiresIn | number | Token TTL in seconds. Default: 3600 |
Manual token minting
If you don't use the middleware, mint tokens directly:
const res = await fetch('https://api.withdialer.com/api/auth/token', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ member_id: 'mem_abc123', expires_in: 3600 }) })
Frontend (React)
The @dialer/react SDK connects your UI to live calls via SSE. One hook gives you everything.
useDialer
The main hook. Connects to the SSE stream, manages call state, and exposes actions.
const { status, // 'connecting' | 'available' | 'on_call' | 'away' activeCall, // Call object or null phase, // 'idle' | 'active' | 'post_call' summary, // AI summary after call ends endedCallId, // ID of the just-ended call (for disposition) endCall, // () => void submitDisposition, // (disposition, notes?) => void setAvailable, // () => void setAway, // () => void } = useDialer({ authUrl: '/dialer-auth' })
| Option | Type | Description |
|---|---|---|
| authUrl* | string | Your backend's token endpoint |
| token | string | Pass a token directly (test/playground use only) |
Components
Pre-built UI components. Fully stylable via className.
| Component | Props | Description |
|---|---|---|
| CallCard | call, onEnd, children, className | Active call display with contact info, timer, and end button |
| LiveTranscript | callId, className | Real-time dual-channel transcript |
| DispositionPicker | onSubmit, suggestedDisposition, summaryText, className | Post-call outcome selector with AI-suggested disposition |
| AgentStatus | status, onAvailable, onAway, className | Available/away toggle |
Full example
import { useDialer, CallCard, LiveTranscript, DispositionPicker, AgentStatus } from '@dialer/react' export default function Agent() { const { activeCall, endCall, status, setAvailable, setAway, summary, phase, endedCallId, submitDisposition } = useDialer({ authUrl: '/dialer-auth' }) return ( <div> <AgentStatus status={status} onAvailable={setAvailable} onAway={setAway} /> {phase === 'post_call' && endedCallId && ( <DispositionPicker onSubmit={(d, notes) => submitDisposition(d, notes)} summaryText={summary?.text} suggestedDisposition={summary?.suggested_disposition} /> )} {activeCall && ( <CallCard call={activeCall} onEnd={endCall}> <LiveTranscript callId={activeCall.id} /> </CallCard> )} {!activeCall && status === 'available' && ( <div>Waiting for a call...</div> )} </div> ) }
Events (SSE)
All state changes stream over Server-Sent Events. No webhooks, no public URLs. Works on localhost.
Endpoints
| Endpoint | Auth | Scope |
|---|---|---|
| GET /api/events | API key | All org events. For backend integrations. |
| GET /api/agent/events | Agent token | Events for one agent. Used by the React SDK automatically. |
Reconnect with Last-Event-Id header to resume from where you left off. Heartbeat every 30s.
Event types
| Event | Description |
|---|---|
| call.ringing | Contact is being dialed |
| call.connected | Call bridged to agent. Includes contact name, phone, metadata. |
| call.ended | Call completed. Includes duration and disposition. |
| call.voicemail | AMD detected voicemail. Call dropped silently. |
| call.recording_ready | Recording URL available (pre-signed, 1hr expiry). |
| call.summary | AI summary and suggested disposition ready (~30s after call). |
| transcript.partial | Live transcription chunk. Speaker labeled. |
| transcript.final | Final transcription line with confidence score. |
| agent.available | Agent went available for calls. |
| agent.on_call | Agent took a call. |
| agent.offline | Agent disconnected. |
| campaign.started | Campaign started dialing. |
| campaign.paused | Campaign paused. |
| campaign.exhausted | All contacts dialed. Includes connect rate and total talk time. |
| sms.sent | SMS follow-up sent (voicemail/no-answer). |
| sms.reply | Inbound SMS from contact. |
Event shape
{
"id": "evt_01hx...",
"type": "call.connected",
"data": {
"call_id": "call_abc",
"campaign_id": "camp_xyz",
"contact": {
"name": "Sarah Chen",
"phone": "+14155550182",
"metadata": { "deal_stage": "Negotiation" }
}
},
"timestamp": "2026-03-31T14:22:08Z"
}
Campaigns
Create a campaign, upload contacts, assign agents, start dialing.
Campaign states
| run_status | Description |
|---|---|
| paused | Default. Campaign is idle. |
| running | Actively dialing contacts. |
| stopped | Permanently stopped. Cannot restart. |
| progress_status | Description |
|---|---|
| not_started | No contacts have been dialed. |
| in_progress | Some contacts remain. |
| exhausted | All contacts attempted (with retries). |
Calls
Dispositions
answered callback_requested not_interested converted voicemail no_answer other
Contacts
Contacts are uploaded via POST /api/campaigns/:id/contacts. Phone numbers must be E.164 format (+14155550182).
Agents
Agent statuses
connecting available on_call away offline
DNC (Do Not Call)
DNC is checked before every dial. Adding a number blocks it immediately across all campaigns.
SMS
SMS follow-ups are sent automatically on voicemail or no-answer (when enabled on the campaign). You can also send manually.