waha-calls
v0.2.1
Published
Client SDK for WhatsApp calls on WAHA (wahacall): config, browser softphone (WebRTC), events and raw PCM. WAHA-native auth, simple event-driven ergonomics.
Maintainers
Readme
waha-calls
Client SDK for WhatsApp calls on WAHA (wahacall). One package: Config (register your AI relay / IVR / reject), Softphone (browser/WebRTC agent panel), Events (incl. the AI→agent handoff), and raw PCM (Node, for a server-side voice bot).
- Auth = WAHA-native (API key + session).
- Ergonomia simples e orientada a eventos (
offer.accept(),call.mute/end/getStats,.on(...)). - Embrulha endpoints que já existem (
/api/apps,/ws,/calls/:id/webrtc,/ws/calls/audio).
Contrato completo:
docs/estudo-sdk-waha-calls.md.
Install
npm install waha-callsConnect
import { WahaCalls } from 'waha-calls';
const waha = new WahaCalls({
baseUrl: 'https://host',
apiKey: 'KEY',
session: 'default', // optional — omit for a multi-session client (see below)
});Config — register the AI relay (or IVR / reject)
await waha.configure({
dm: { mode: 'relay', waitBeforeAnswer: 3, relay: { url: 'wss://my-bot/voice' } },
group: { mode: 'reject' },
});Events + agent handoff
// Incoming call → answer in the softphone
waha.on('offer', async (offer) => {
const { call } = await offer.accept(); // answers + connects WebRTC audio
agentAudioElement.srcObject = call!.stream;
});
// AI handed off → agent takes over the same call
waha.on('escalate', async ({ callId, summary }) => {
showHandoff(summary);
const { call } = await waha.grab(callId); // connects WebRTC, displaces the AI
agentAudioElement.srcObject = call!.stream;
call!.on('ended', cleanup);
});Call (active)
call.mute(); call.unmute(); call.end();
call.on('connectionStatus', (s) => {}); // 'connecting' | 'connected' | 'disconnected'
call.on('ended', () => {});
const stats = await call.getStats(); // { rtt, rxLoss, jitterMs, ... }Node / raw PCM (server-side voice bot)
In Node there is no browser WebSocket, so pass one — install ws
and inject it. Then take a live call as raw PCM (no WebRTC):
npm install waha-calls wsimport { WebSocket } from 'ws';
import { WahaCalls } from 'waha-calls';
const waha = new WahaCalls({ baseUrl, apiKey, session, webSocketImpl: WebSocket });
waha.on('call', async ({ id }) => {
const pcm = await waha.grabPcm(id);
pcm.onAudio((frame) => stt.write(Buffer.from(frame))); // caller → you
setInterval(() => pcm.sendAudio(ttsFrame()), 60); // you → caller
pcm.on('ended', cleanup);
});Audio is s16le / 16 kHz / mono — 1920 bytes (960 samples) per 60 ms frame (same framing as the
relay). The browser entry point works without webSocketImpl (uses the native WebSocket); the
injection is only needed in Node. Full runnable example: examples/node-bot.mjs.
Multi-session (many WhatsApp numbers in one client)
Omit session in the constructor and the client subscribes to all sessions (*) over one
events WebSocket. Each event carries its session, and methods take a session argument. Ideal
for a multi-tenant backend (e.g. several inboxes/accounts):
const waha = new WahaCalls({ baseUrl, apiKey }); // no session → all of them
waha.on('offer', async (offer) => {
console.log('ringing on', offer.session, 'from', offer.peer.phone);
const { call } = await offer.accept(); // offer already knows its session
});
waha.on('escalate', async ({ session, callId }) => {
const { call } = await waha.grab(callId, undefined, session); // pass the session
});
// config / placeCall / grab / grabPcm take the session explicitly here
await waha.configure({ dm: { mode: 'relay', relay: { url } } }, 'vendas');
await waha.placeCall('[email protected]', 'suporte');Requires an API key with access to all sessions (a global key). With a single-session key the
server scopes * down to that one session automatically. In multi-session mode, calling a method
without a session throws — there is no default.
Status
Browser softphone + config + events + handoff (grab) + Node raw-PCM (grabPcm) + multi-session.
Zero runtime dependencies. See docs/estudo-sdk-waha-calls.md for the full contract.
