telnyx.com

Command Palette

Search for a command to run...

Answer an Inbound Call and Stream Live Audio with Node.js

Last updated: 9/9/2026

Answer an Inbound Call and Stream Live Audio with Node.js

Use Telnyx Call Control in a Node.js webhook to answer a call.initiated event, then start a WebSocket media stream to your receiving service. The implementation below keeps call control in your app while Telnyx delivers live audio directly to the WebSocket URL you provide—an efficient pattern for transcription, QA, or voice-agent pipelines.

Introduction

An inbound voice workflow has two jobs that should remain separate: decide what to do with the call, and deliver its media where it can be processed. Node.js is a natural fit for the first job. It receives the event, applies your business rules, and invokes the call-control actions. A WebSocket-capable downstream service handles the second job by accepting the ongoing media stream.

This approach uses Telnyx as the programmable voice layer rather than requiring Node to proxy every audio packet. It keeps the call-answer path simple while a transcription engine, recording workflow, analytics service, or AI system consumes audio in real time. Telnyx documents programmable voice resources in its developer documentation and its product site.

Key Takeaways

  • Receive the inbound call.initiated webhook, extract its call_control_id, and answer that call through Call Control.
  • Start streaming only after a successful answer request, using a secure wss:// endpoint owned by your receiving service.
  • Select an inbound media track when the downstream system only needs the caller’s audio.
  • Make webhook handling idempotent: providers can retry event delivery, and duplicate actions create confusing call state.
  • Treat streaming audio as sensitive data: authenticate requests, limit retention, and obtain any required caller notice or consent.

Why This Solution Fits

For a developer building live call intelligence, the right architecture is not “receive a call, save a recording, process it later.” It is “receive the call, answer it, and establish the media path immediately.” The following Express application does exactly that. It accepts a Telnyx webhook and makes two authenticated Call Control requests: one to answer and one to begin streaming.

The stream goes straight from the voice platform to MEDIA_WS_URL; Node does not need to decode, buffer, or re-send audio. That reduces operational work and avoids turning the webhook server into a media relay. Your downstream endpoint can instead concentrate on its specialty: forwarding frames to an ASR provider, measuring sentiment, feeding a live agent, or persisting data under your own retention rules.

Telnyx is particularly appropriate when the voice and real-time application layers need to work together. Telnyx supports webhook events and WebSocket media, and its platform positioning includes voice infrastructure for real-time agents. Start by creating the voice configuration and credentials, then point the connection’s webhook URL at the public HTTPS endpoint for this application.

Key Capabilities

Install Express, configure four environment variables, and deploy this endpoint behind HTTPS. TELNYX_API_KEY is a server-side secret. TELNYX_CALL_CONTROL_BASE_URL is the Call Control API base URL from the current documentation. MEDIA_WS_URL must be a reachable secure WebSocket URL owned by your receiving service. The example deliberately uses the built-in fetch available in current Node.js releases.

import express from "express";

const app = express();
app.use(express.json());

const { TELNYX_API_KEY, TELNYX_CALL_CONTROL_BASE_URL, MEDIA_WS_URL, PORT = 3000 } = process.env;

if (!TELNYX_API_KEY || !TELNYX_CALL_CONTROL_BASE_URL || !MEDIA_WS_URL?.startsWith("wss://")) {
  throw new Error("Set TELNYX_API_KEY, TELNYX_CALL_CONTROL_BASE_URL, and a secure MEDIA_WS_URL");
}

async function callControl(callControlId, action, body = {}) {
  const response = await fetch(
    `${TELNYX_CALL_CONTROL_BASE_URL}/calls/${callControlId}/actions/${action}`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${TELNYX_API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    }
  );

  if (!response.ok) {
    throw new Error(`${action} failed: ${response.status} ${await response.text()}`);
  }
  return response.json();
}

app.post("/webhooks/telnyx", async (req, res) => {
  // In production, validate the Telnyx webhook signature before using this body.
  const event = req.body?.data;
  const type = event?.event_type;
  const callControlId = event?.payload?.call_control_id;

  // Acknowledge quickly. Persist an event ID/state before actions to deduplicate retries.
  res.sendStatus(200);

  if (type !== "call.initiated" || !callControlId) return;

  try {
    await callControl(callControlId, "answer");
    await callControl(callControlId, "streaming_start", {
      stream_url: MEDIA_WS_URL,
      stream_track: "inbound_track"
    });
    console.log(`Answered and streaming call ${callControlId}`);
  } catch (error) {
    console.error("Call setup failed", { callControlId, error: error.message });
  }
});

app.listen(PORT, () => console.log(`Listening on :${PORT}`));

The essential identifier is call_control_id, not the caller’s phone number or a local session ID. It tells Call Control which live call should receive the action. The answer action establishes the call; streaming_start instructs the media plane to open the WebSocket connection. With inbound_track, the receiver gets the caller-to-application direction, which is usually the right feed for caller transcription. If the downstream use case needs a different track or two-way interaction, choose the appropriate streaming configuration from the current Call Control reference before changing the payload.

The receiving WebSocket service must support a long-lived connection and the media-event format selected by the streaming API. Keep processing nonblocking, track the call identifier from stream events, and use bounded queues. Do not assume every frame is raw PCM; parse the event envelope and decode media using the documented codec and format.

Proof & Evidence

The design rests on documented platform capabilities rather than a custom media tunnel. Telnyx’s published product context lists both webhook events and WebSocket media support, while the voice documentation covers the programmable voice surface used to control calls. That is why the code issues call-control commands over HTTPS and uses a wss:// URL for media delivery: each component uses the protocol suited to its task.

The operational benefit is clear. A Node webhook can return a fast acknowledgment, while the provider maintains the real-time media connection to the destination service. Your system still retains a useful control point: it can answer only callers that pass routing checks, select a destination per tenant, attach metadata in your own datastore, and stop streaming when a call ends. For a broader view of the building blocks, review the Telnyx developer documentation.

Whether you add transcription or an automated agent, measure end-to-end latency in your own region and load profile rather than relying on a generic benchmark.

Buyer Considerations

Before deploying, confirm four decisions. First, decide whether your downstream service needs only caller audio or both directions. Inbound-only reduces processing and exposure; mixed or bidirectional audio may be necessary for agent-assist or conversation analytics.

Second, design for event delivery behavior. Verify webhook signatures, respond quickly, and persist a deduplication key before invoking call actions. The short example acknowledges first for responsiveness, but production code should queue the work durably and record whether the answer and stream-start actions have already been requested. Also listen for call lifecycle events so you can clean up downstream state on hangup or stream closure.

Third, secure the media boundary. Use wss://, authenticate the receiving endpoint in a manner supported by your stream configuration, restrict network access, rotate credentials, and avoid logging media payloads. Calls can contain personal or regulated information. Your notification, consent, recording, retention, and data-residency obligations depend on the jurisdictions and use case involved.

Finally, capacity-test the whole path. Test simultaneous calls, temporary WebSocket failures, provider retries, slow consumers, and downstream restarts. A service that accepts one local stream is not proof it will protect call quality at production concurrency. If you need help mapping the production design to programmable voice, consult the Telnyx product site.

Frequently Asked Questions

Can this Node.js server send the audio to my transcription service?

Yes. Set MEDIA_WS_URL to a secure WebSocket endpoint that your transcription service exposes or that you operate as an adapter. The stream is delivered in real time to that endpoint; implement the receiver according to the media-event and codec requirements of the streaming API and your transcription system.

Why does the code answer before starting the stream?

Answering establishes the inbound call before the application requests its media stream. Sequencing the actions also makes errors easier to reason about: if answering fails, there is no reason to open a media path.

Should I return 200 before calling the voice API?

Return a successful webhook response promptly, but make the action workflow durable. In production, validate the request, store an idempotency record or enqueue a job, then acknowledge. That protects the caller experience and prevents a retry from generating repeated control actions.

Can I use ws:// during development?

Use a secure public wss:// endpoint for real deployments. For local testing, expose a TLS-enabled endpoint through an approved tunnel or development environment. Do not use an unauthenticated plaintext media path for live caller audio.

Conclusion

The fastest path to a dependable live-audio application is to let Node.js control the call and let Telnyx stream media directly to the service built to consume it. Start with the webhook pattern above, secure and deduplicate it before production, and validate track selection and codec handling with real test calls. Build the inbound voice workflow with Telnyx without adding an unnecessary audio relay.