Listeners
When your voice app receives a call.notify webhook from a notify-mode node, you can create a listener to stream real-time audio from the call. Audio flows one way only — from DialStack to your server; the listener is passive and does not inject audio or alter the call. Neither party hears a tone or indication that a listener is attached, so you are responsible for obtaining appropriate consent from the parties on the call in accordance with applicable law (recording-consent requirements vary by jurisdiction).
Text
┌────────┐ ┌───────────┐ ┌─────────────┐
│ Caller │ │ DialStack │ │ Your Server │
└───┬────┘ └─────┬─────┘ └──────┬──────┘
│ │ │
│ Normal two-party call │ │
│◀─────────────────────────▶│ │
│ │ │
│ │ 1. Webhook (call.notify) │
│ │──────────────────────────▶│
│ │ │
│ │ 2. POST /v1/calls/{id}/ │
│ │ listeners │
│ │◀──────────────────────────│
│ │ │
│ Call continues normally │ 3. Audio (one-way WSS) │
│◀─────────────────────────▶│──────────────────────────▶│
│ │ │
- A Voice App (Notify) node notifies your server that a call has started
- Your server creates a listener on that call
- DialStack opens a WebSocket to your server and streams audio
Creating a Listener
- SDK
- cURL
JavaScript
const listener = await dialstack.calls.createListener(
callId,
{
url: 'wss://your-server.example.com/audio',
channel: 'both',
},
{ dialstackAccount: accountId }
);
Bash
curl -X POST https://api.dialstack.ai/v1/calls/call_01h2xcejqtf2nbrexx3vqjhp45/listeners \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{
"url": "wss://your-server.example.com/audio",
"channel": "both"
}'
Channel Selection
| Channel | Audio received |
|---|---|
caller | Audio from the party that initiated the call |
callee | Audio from the party that received the call |
both | Both channels, delivered as separate tagged messages |
Stopping a Listener
Listeners stop automatically when the call ends. To stop early:
- SDK
- cURL
JavaScript
await dialstack.calls.delListener(callId, listenerId, {
dialstackAccount: accountId,
});
Bash
curl -X DELETE https://api.dialstack.ai/v1/calls/call_01h2xcejqtf2nbrexx3vqjhp45/listeners/lstn_01h2xcejqtf2nbrexx3vqjhp50 \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"
Listener WebSocket Protocol
A listener session uses the same WebSocket protocol as attach, one way only: DialStack sends, and does not read what you send back. Three things are specific to listeners:
- The
beginmessage carrieslistener_idinstead ofvoice_app_id, which lets one server handle both kinds of session. - With
channel: "both", eachaudiomessage carries achannelfield,callerorcallee. - Before closing the socket, DialStack sends an
endmessage whosereasoniscall_ended,deleted(stopped with the API) orerror.
Complete Example: Real-Time Transcription
JavaScript
import express from 'express';
import { WebSocketServer } from 'ws';
import { DialStack } from '@dialstack/sdk-server';
const app = express();
const dialstack = new DialStack(process.env.DIALSTACK_API_KEY);
const VOICE_APP_SECRET = process.env.VOICE_APP_SECRET;
// Webhook endpoint — receives call.notify from Voice App (Notify) dial plan node
app.post('/voice/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
let event;
try {
event = DialStack.webhooks.constructEvent(
req.body,
req.headers['x-dialstack-signature'],
VOICE_APP_SECRET
);
} catch (err) {
return res.sendStatus(401);
}
res.sendStatus(200);
if (event.event === 'call.notify') {
// Create a listener to stream audio for transcription
await dialstack.calls.createListener(
event.call_id,
{
url: 'wss://your-server.example.com/audio',
channel: 'both',
},
{ dialstackAccount: event.account_id }
);
}
});
// WebSocket server for receiving listener audio
const wss = new WebSocketServer({ noServer: true });
wss.on('connection', (ws) => {
let listenerId;
ws.on('message', (data) => {
const message = JSON.parse(data);
switch (message.event) {
case 'begin':
listenerId = message.listener_id;
console.log(`Listening to call ${message.call_id}`);
break;
case 'audio':
// Send to your speech-to-text service
transcribe(message.payload, message.channel);
break;
case 'end':
console.log(`Listener ${listenerId} stopped: ${message.reason}`);
break;
}
});
});
const server = app.listen(3000);
server.on('upgrade', (request, socket, head) => {
if (request.url === '/audio') {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
} else {
socket.destroy();
}
});
See also
- Voice Apps — creating a voice app and the notify-mode dial plan node.
- Call Control — take over the call instead of listening to it.
- WebSocket API — the audio streaming protocol.
- Listeners API — create, list and stop listeners.