> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.jambonz.org/sdks/node-sdk/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.jambonz.org/_mcp/server. # Node.js SDK The `@jambonz/sdk` package is the recommended way to build jambonz voice applications in Node.js/TypeScript. It supports both webhook and WebSocket transports, a REST API client for mid-call control, and chainable verb methods for building call flows. **Source code:** [github.com/jambonz/node-sdk](https://github.com/jambonz/node-sdk) **API reference:** [jambonz.github.io/node-sdk](https://jambonz.github.io/node-sdk/) > **Note** > > This SDK replaces the older `@jambonz/node-client` (webhook) and `@jambonz/node-client-ws` (WebSocket) packages, which are now [deprecated](/sdks/nodeclient). The new SDK provides a unified package with a consistent API across both transports. > **Info** > > **Which transport should I use?** The **WebSocket transport** is recommended for most applications. It provides a persistent bidirectional connection that enables TTS streaming, mid-call updates, inject commands, real-time event handling, and voice AI features like the [agent](/verbs/verbs/agent) and [llm](/verbs/verbs/llm) verbs. The **webhook transport** is simpler but limited — use it for straightforward call routing scenarios (e.g., [dial](/verbs/verbs/dial), basic IVR menus) where you don't need real-time interaction. ## Installation ```bash npm install @jambonz/sdk ``` ## Imports The SDK provides three subpath exports: ```typescript // Webhook apps (Express/HTTP) import { WebhookResponse } from '@jambonz/sdk/webhook'; // WebSocket apps import { createEndpoint } from '@jambonz/sdk/websocket'; // REST API client (mid-call control, outbound calls) import { JambonzClient } from '@jambonz/sdk/client'; ``` ## Webhook Transport Use `WebhookResponse` to build verb arrays in response to HTTP webhooks. Methods are chainable and the response is serialized to JSON. ```typescript import express from 'express'; import { WebhookResponse } from '@jambonz/sdk/webhook'; const app = express(); app.use(express.json()); app.post('/incoming', (req, res) => { const jambonz = new WebhookResponse(); jambonz .say({ text: 'Hello from jambonz!' }) .gather({ input: ['speech', 'digits'], actionHook: '/handle-input', say: { text: 'Press 1 for sales or 2 for support.' }, }) .hangup(); res.json(jambonz); }); app.post('/handle-input', (req, res) => { const jambonz = new WebhookResponse(); const speech = req.body.speech?.alternatives?.[0]?.transcript; jambonz.say({ text: `You said: ${speech}` }).hangup(); res.json(jambonz); }); app.listen(3000); ``` ## WebSocket Transport Use `createEndpoint` to build real-time WebSocket applications. This is the recommended transport for voice AI agents, as it enables bidirectional communication, event streaming, and mid-call updates. ```typescript import http from 'http'; import { createEndpoint } from '@jambonz/sdk/websocket'; const server = http.createServer(); const makeService = createEndpoint({ server, port: 3000 }); const svc = makeService({ path: '/' }); svc.on('session:new', (session) => { // Bind actionHook handlers first session.on('/gather-result', (evt) => { const transcript = evt.speech?.alternatives?.[0]?.transcript || ''; session.say({ text: `You said: ${transcript}` }).hangup().reply(); }); // Send initial verbs session .say({ text: 'Hello! Say something.' }) .gather({ input: ['speech'], actionHook: '/gather-result', timeout: 10 }) .hangup() .send(); }); ``` ### .send() vs .reply() * **`.send()`** — Use once for the initial verb array in response to `session:new`. * **`.reply()`** — Use for all subsequent responses to actionHook events. This distinction is important: `.send()` starts the call flow, while `.reply()` continues it in response to events. ### Application Environment Variables You can declare environment variables that are configurable in the jambonz portal UI: ```typescript const makeService = createEndpoint({ server, port: 3000, envVars: { OPENAI_MODEL: { type: 'string', description: 'LLM model to use', default: 'gpt-4.1-mini', }, SYSTEM_PROMPT: { type: 'string', description: 'System prompt', uiHint: 'textarea', default: 'You are a helpful assistant.', }, }, }); // Read values in session handler svc.on('session:new', (session) => { const model = session.data.env_vars?.OPENAI_MODEL || 'gpt-4.1-mini'; // ... }); ``` ## Audio Streams When using the [listen](/verbs/verbs/listen) verb, `makeService.audio()` lets you handle both call control and audio on the same server: ```typescript const svc = makeService({ path: '/' }); const audioSvc = makeService.audio({ path: '/audio-stream' }); svc.on('session:new', (session) => { session .say({ text: 'Listening...' }) .listen({ url: '/audio-stream', sampleRate: 8000, bidirectionalAudio: { enabled: true, streaming: true, sampleRate: 8000 }, }) .send(); }); audioSvc.on('connection', (stream) => { stream.on('audio', (pcm) => { // Process audio — feed to STT, record, etc. }); // Send audio back stream.sendAudio(pcmBuffer); stream.on('close', () => console.log('Audio stream closed')); }); ``` The `AudioStream` object provides `sendAudio()`, `playAudio()`, `killAudio()`, `disconnect()`, `sendMark()`, and `clearMarks()` methods. ## REST API Client Use `JambonzClient` for outbound calls and mid-call control: ```typescript import { JambonzClient } from '@jambonz/sdk/client'; const client = new JambonzClient({ baseUrl: 'https://api.jambonz.us', accountSid: 'your-account-sid', apiKey: 'your-api-key', }); // Create an outbound call await client.calls.create({ from: '+15085551212', to: { type: 'phone', number: '+15085551213' }, call_hook: '/incoming', }); // Mid-call control await client.calls.mute(callSid, 'mute'); await client.calls.redirect(callSid, 'https://example.com/new-flow'); ``` ## Verb Methods Both `WebhookResponse` and WebSocket `Session` support the same chainable verb methods: `.say()` `.play()` `.gather()` `.dial()` `.llm()` `.agent()` `.conference()` `.enqueue()` `.dequeue()` `.hangup()` `.pause()` `.redirect()` `.config()` `.tag()` `.dtmf()` `.listen()` `.transcribe()` `.message()` `.dub()` `.alert()` `.answer()` `.leave()` `.sipDecline()` `.sipRefer()` `.sipRequest()` All methods accept the same options as the corresponding [verb JSON schemas](/verbs/verbs/overview) and are chainable. ## TTS Token Streaming The WebSocket Session provides methods for incremental TTS token streaming, enabling the lowest-latency voice AI experiences. This is used when you're streaming tokens from an LLM and want them spoken as they arrive. ```typescript session.on('/llm-tokens', async (evt) => { const { tokens, done } = evt; if (tokens) { // Send tokens as they arrive from the LLM — backpressure is handled automatically await session.sendTtsTokens(tokens); } if (done) { // Signal end of token stream session.flushTtsTokens(); } }); ``` | Method | Returns | Description | | --------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `sendTtsTokens(text)` | `Promise` | Send a chunk of text for TTS. Resolves when jambonz acknowledges receipt. Automatically applies backpressure if the buffer is full. | | `flushTtsTokens()` | `void` | Signal the end of a TTS token stream. Triggers final audio generation. | | `clearTtsTokens()` | `void` | Cancel all pending TTS tokens, clear the queue, and reset backpressure state. | The `isTtsPaused` property indicates whether TTS streaming is paused due to backpressure. ### TTS Streaming Events | Event | Description | | ----------------------- | --------------------------------------------- | | `tts:stream_open` | TTS vendor connection established | | `tts:stream_paused` | Backpressure — buffer full, tokens will queue | | `tts:stream_resumed` | Backpressure released, streaming resumes | | `tts:stream_closed` | TTS stream ended | | `tts:user_interruption` | User barged in during TTS playback | ## LLM and Agent Updates The Session provides methods for interacting with active LLM and agent conversations. ### Tool Output When the LLM requests a tool/function call (via the `toolHook`), respond with the result: ```typescript session.on('/tool-call', async (evt) => { const { tool_call_id, name, arguments: args } = evt; const result = await handleTool(name, args); session.sendToolOutput(tool_call_id, result); }); ``` ### Agent Updates Send mid-conversation updates to an active [agent](/verbs/verbs/agent): ```typescript // Change the system prompt session.updateAgent({ type: 'update_instructions', instructions: 'You are now a billing agent.', }); // Inject context session.updateAgent({ type: 'inject_context', messages: [{ role: 'user', content: 'Customer: Sarah, Gold tier.' }], }); // Replace tools session.updateAgent({ type: 'update_tools', tools: [...] }); // Trigger a new response (with optional interrupt) session.updateAgent({ type: 'generate_reply', interrupt: true, user_input: 'Tell the customer about the flash sale.', }); ``` ### LLM Updates Send updates to an active [llm](/verbs/verbs/llm) verb: ```typescript session.updateLlm({ instructions: 'Switch to Spanish.' }); ``` | Method | Description | | ---------------------------------- | ------------------------------------------------------------------------------------------------------ | | `sendToolOutput(toolCallId, data)` | Send tool/function result back to the LLM or agent verb | | `updateAgent(data)` | Send an `agent:update` command (update\_instructions, inject\_context, update\_tools, generate\_reply) | | `updateLlm(data)` | Send an `llm:update` command | ## Inject Commands Inject commands execute immediately on an active call without affecting the verb stack. They are useful for mid-call control actions like muting, recording, or whispering to one party on a bridged call. ```typescript // Mute/unmute session.injectMute('mute'); session.injectMute('unmute'); // Whisper to one party (e.g., coaching a call center agent) session.injectWhisper({ verb: 'say', text: 'The customer is a VIP.' }, agentCallSid); // Control noise isolation mid-call session.injectNoiseIsolation('enable', { vendor: 'krisp', level: 80 }); session.injectNoiseIsolation('disable'); // Control recording session.injectRecord('startCallRecording', { siprecServerURL: 'sip:siprec@recorder.example.com' }); session.injectRecord('pauseCallRecording'); // Pause/resume audio streaming session.injectListenStatus('pause'); session.injectListenStatus('resume'); // Send DTMF session.injectDtmf('1234'); // Redirect call flow session.injectRedirect('/new-webhook'); // Tag the call with metadata session.injectTag({ priority: 'high', department: 'billing' }); ``` | Method | Description | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `injectMute(status)` | Mute or unmute the call (`'mute'` or `'unmute'`) | | `injectWhisper(verb, callSid?)` | Play a whisper verb (say/play) to one party on a bridged call | | `injectNoiseIsolation(status, opts?, callSid?)` | Enable or disable noise isolation. Options: `vendor`, `level`, `model` | | `injectRecord(action, opts?, callSid?)` | Control call recording: `startCallRecording`, `stopCallRecording`, `pauseCallRecording`, `resumeCallRecording` | | `injectListenStatus(status, callSid?)` | Pause or resume audio streaming (`'pause'` or `'resume'`) | | `injectDtmf(digit, duration?, callSid?)` | Send DTMF digits into the call | | `injectRedirect(hook, callSid?)` | Redirect call execution to a new webhook | | `injectTag(data, callSid?)` | Attach metadata to the call | | `injectCommand(command, data?, callSid?)` | Send a generic inject command | > **Note** > > The optional `callSid` parameter on inject methods targets a specific call leg on a bridged call. Omit it to target the current call. ## Session Properties | Property | Type | Description | | ---------------- | ------------------------- | --------------------------------------------------------------- | | `callSid` | `string` | Unique call identifier | | `from` | `string` | Caller phone number or SIP URI | | `to` | `string` | Called phone number or SIP URI | | `direction` | `'inbound' \| 'outbound'` | Call direction | | `accountSid` | `string` | Account identifier | | `applicationSid` | `string` | Application identifier | | `callId` | `string` | SIP Call-ID | | `data` | `CallSession` | Full call session data (includes `env_vars`, SIP headers, etc.) | | `locals` | `Record` | Application-specific storage that persists for the session | | `isTtsPaused` | `boolean` | Whether TTS streaming is paused due to backpressure | ## Session Events | Event | Description | | --------------- | --------------------------------------------------- | | `'/hookName'` | ActionHook callback — requires `.reply()` | | `verb:status` | Verb status change (when `notifyEvents` is enabled) | | `call:status` | Call state change | | `jambonz:error` | Error from jambonz | | `close` | WebSocket connection closed | | `error` | WebSocket connection error | ## AI-Assisted Development The `@jambonz/mcp-schema-server` package is an MCP server that gives AI coding assistants deep knowledge of jambonz APIs, verb schemas, and SDK patterns. Set it up so your AI can generate correct jambonz code automatically. **Remote server (simplest):** ```bash claude mcp add jambonz -t http https://mcp-server.jambonz.app/mcp ``` **Local via npx:** ```bash claude mcp add jambonz -- npx -y @jambonz/mcp-schema-server ``` For Cursor, VS Code, and other editors, see the [setup instructions](https://github.com/jambonz/node-sdk#setup) in the repository. A complementary [Agent Skill](https://github.com/jambonz/skills) provides procedural knowledge about jambonz patterns and best practices: ```bash npx skills add jambonz/skills ``` ## Examples See the [examples directory](https://github.com/jambonz/node-sdk/tree/main/examples) for runnable demos: | Example | Transport | Description | | --------------- | ------------ | ---------------------------------------- | | hello-world | Webhook + WS | Minimal greeting | | echo | Webhook + WS | Speech echo using gather | | ivr-menu | Webhook | Interactive menu with speech and DTMF | | voice-agent | Webhook + WS | LLM-powered conversational AI with tools | | openai-realtime | WebSocket | OpenAI Realtime API voice agent | | llm-streaming | WebSocket | LLM with TTS streaming and barge-in | For agent verb examples, see the [agent examples](https://github.com/jambonz/v10-examples/tree/main/examples/agent). > Build jambonz voice applications with @jambonz/sdk