AI Summary: This article walks backend engineers through integrating the Deepgram API for live call transcription over WebSocket, covering the full audio path from PSTN carrier to the Deepgram wss://api.deepgram.com/v2/listen endpoint. Deepgram requires explicit encoding and sample_rate parameters for raw audio streams; omitting either causes a decode failure. Readers must decide how audio terminates, whether in a media server, a WebRTC gateway, or a direct WebSocket bridge, before writing a line of code. Teler handles the carrier and media-streaming layer so the application server receives a clean WebSocket audio feed ready to forward to Deepgram.
Scope: This walkthrough covers streaming audio from a live PSTN call to the Deepgram API v2 listen endpoint (wss://api.deepgram.com/v2/listen), verified against Deepgram documentation and SDK releases current as of September 2026. It does not cover Deepgram’s pre-recorded REST transcription or its voice-agent WebSocket interface.
Quick Answer: The Deepgram API for live call transcription uses a persistent WebSocket connection to wss://api.deepgram.com/v2/listen. You stream raw binary audio frames, tag each connection with the correct encoding and sample_rate, and receive JSON transcript events in return. In 2026, the Flux model family handles turn detection natively.
The Deepgram API exposes real-time speech-to-text over a WebSocket connection, and connecting it to a live phone call requires bridging two incompatible transport worlds: the carrier’s RTP stream and Deepgram’s binary WebSocket frames. Getting that bridge right is the entire integration problem.
What is the Deepgram API?
Deepgram is a cloud speech-to-text and voice AI platform that exposes real-time transcription over WebSocket for application developers. It occupies the STT layer of a voice stack: it accepts raw or containerized audio and returns transcript events; it does not provision phone numbers, handle SIP signaling, or carry RTP. A developer using the Deepgram API must supply the transport, number provisioning, resampling, jitter handling, call control, DTMF processing, and recording infrastructure themselves. Deepgram’s live listen endpoint is wss://api.deepgram.com/v2/listen, and the SDK wraps that connection with typed send methods including sendMedia, sendFinalize, sendCloseStream, and sendKeepAlive.
Why is live call transcription with the Deepgram API harder than it looks?
Telephony audio and WebSocket audio streaming have different assumptions baked in at every layer. A PSTN call typically carries narrowband PCM at 8000 Hz encoded as G.711 mu-law or A-law, as defined in ITU-T G.711, while Deepgram’s Flux model recommends 80-millisecond chunks and accepts linear16, mulaw, alaw, opus, ogg-opus, and linear32. The codec may match, but the framing, packetization, and transport never do.
Beyond codec translation, the WebSocket protocol (RFC 6455) and RTP (RFC 3550) have different reliability and ordering guarantees. RTP tolerates packet loss by design; WebSocket over TCP retransmits, which introduces head-of-line blocking under congestion. That gap means your media bridge must absorb jitter, a short buffer that smooths out arrival variance, before forwarding audio to Deepgram. If you skip the jitter buffer, bursty packet loss from the carrier produces corrupted frames that Deepgram closes with 1008 DATA-0000.
What does the audio path look like from carrier to Deepgram?
The end-to-end path has five hops. Each hop has an owner, a format, and a transport. Putting comparable facts in a table prevents the kind of format mismatch that causes a silent failure at 3 AM.
| Hop | From | To | Format | Transport | Owner |
|---|---|---|---|---|---|
| 1 | PSTN caller | Carrier edge | G.711 mu-law, 8000 Hz | PSTN / SS7 | Carrier |
| 2 | Carrier edge | Media server or SBC | RTP, G.711 or Opus | UDP / RTP per RFC 3550 | Telephony provider |
| 3 | Media server | Application server | PCM linear16 or mu-law, 8000 Hz | WebSocket | Telephony provider |
| 4 | Application server | Deepgram v2 listen | linear16 / mulaw / alaw / opus, tagged with encoding + sample_rate | WebSocket, wss://api.deepgram.com/v2/listen | Telephony provider |
| 5 | Deepgram | Application server | JSON transcript events | WebSocket text frames | Deepgram |
Teler handles hops 2 and 3. When a call arrives on a Teler number,.the platform streams base64-encoded PCM16 audio over WebSocket to your application server at either 8k or 16k sample rate, in configurable chunks of 20 to 2000 milliseconds. Your server then forwards those frames directly to Deepgram at hop 4, which is the only hop you write code for.
What do you need before writing code?
- A Deepgram account and API key from developers.deepgram.com. Store it as an environment variable; the SDK reads it from there.
- A telephony provider that exposes raw audio over WebSocket. Teler does this natively. If you use a SIP trunk directly, you need a media server such as FreeSWITCH or Asterisk to bridge RTP to WebSocket.
- Python 3.9 or later, or Node.js 18 or later, for the official SDKs.
- The Deepgram Python SDK:
pip install deepgram-sdk. Or the JavaScript SDK:npm install @deepgram/sdk. Go, .NET, Java, and Rust SDKs are also available. - Network reachability from your application server to
wss://api.deepgram.comon port 443. Firewall rules that block outbound WebSocket upgrades are a common first blocker. - Audio arriving at your server in a known format. If you do not know whether the carrier sends mu-law or linear PCM, check before writing the Deepgram connection parameters. A format mismatch is the single most common cause of silent failures.
How do you implement the Deepgram API for live call transcription?
Step 1: Open the Deepgram WebSocket connection
Open the Deepgram connection before audio capture starts. If audio arrives at your server before the socket is ready, the first frame is lost and Deepgram may treat the stream as corrupt. Buffer early chunks and hold them until the OPEN event fires.
import os
from deepgram import DeepgramClient
from deepgram.clients.listen.v1.websocket import LiveOptions
DEEPGRAM_API_KEY = os.environ["DEEPGRAM_API_KEY"]
client = DeepgramClient(DEEPGRAM_API_KEY)
options = LiveOptions(
model="flux-general-en",
encoding="linear16",
sample_rate=8000,
channels=1,
smart_format=True,
)
connection = client.listen.v1.websocket.connect(options) The encoding and sample_rate values must match the audio your telephony layer delivers exactly. Deepgram does not infer these from raw streams.
Step 2: Register event handlers
The SDK exposes OPEN, MESSAGE, CLOSE, and ERROR event hooks. Register all four before sending audio. The MESSAGE handler receives JSON transcript events; parse the channel.alternatives[0].transcript field for the text.
from deepgram.core.events import EventType
def on_message(self, result, **kwargs):
transcript = result.channel.alternatives[0].transcript
if transcript:
print(f"Transcript: {transcript}")
def on_error(self, error, **kwargs):
print(f"Deepgram error: {error}")
connection.on(EventType.MESSAGE, on_message)
connection.on(EventType.ERROR, on_error) Register the ERROR handler first; an unhandled error event before OPEN fires can silently drop the connection in some async runtimes.
Step 3: Forward audio frames from the telephony WebSocket
When your telephony provider, such as Teler, delivers audio over its own WebSocket, forward each binary frame to Deepgram using sendMedia. Deepgram’s Flux model recommends 80-millisecond chunks for optimal latency. Teler’s default chunk size is 400 milliseconds, so you may want to configure it closer to 80 ms for tighter turn detection.
import base64
async def on_telephony_audio(audio_chunk_b64: str):
# Teler sends base64-encoded PCM16 at 8000 Hz; decode to raw bytes for Deepgram
audio_bytes = base64.b64decode(audio_chunk_b64)
connection.send(audio_bytes) Send real audio during silence rather than skipping frames. Deepgram closes the stream with 1011 NET-0002 if no binary audio arrives within its timeout window, even if the socket is still open and you are sending KeepAlive messages.
Step 4: Close the stream gracefully
When the call ends, send a close stream message before closing the WebSocket. An ungraceful close leaves the stream state unusable for the next session, and reconnecting to a dead stream returns no transcriptions.
connection.send_close_stream()
connection.finish() Call send_close_stream before finish; reversing the order drops any buffered transcript events that Deepgram has not yet flushed.
Deepgram maintains two vendor-official reference implementations that show this pattern end-to-end with Twilio as the telephony layer: deepgram-voice-agent-inbound-telephony (last commit March 23, 2026) and deepgram-voice-agent-outbound-telephony (last commit March 5, 2026). Both use Deepgram Flux for STT with native turn-taking. Review the audio bridging code there before writing your own media loop. For Teler-specific WebSocket audio handling, see the Teler developer documentation.
Most teams discover their latency problem is not the model, it is the resampling hop they added between the carrier and the STT engine. If the telephony layer already delivers mu-law at 8000 Hz and Deepgram accepts mu-law at 8000 Hz, the right move is to forward the frames directly and skip the transcode entirely.
– Subhash Kalluri, Founder & CEO, FreJun
What breaks in production when using the Deepgram API for live calls?
The failure modes below come from documented GitHub discussions and Deepgram’s own troubleshooting reference. Each one has a source URL; frequency is stated honestly.
| Symptom | Root cause | Fix | Frequency |
|---|---|---|---|
Stream closes with 1011 NET-0002 during silence | No binary audio reached Deepgram within the timeout window; KeepAlive alone does not prevent closure | Send real or silent audio during gaps; send CloseStream when done | Reported once |
| Reconnect succeeds but no transcriptions return | Prior socket was closed ungracefully, leaving stream state unusable | Close the stream gracefully with sendCloseStream before reconnecting | Reported once |
| Sporadic empty results or corrupt-stream close | Audio capture started before the socket was fully open; first frame or header lost | Open the connection first, buffer early chunks, send only after OPEN fires | Reported twice |
| Socket connects but transcripts stay empty | Binary audio sent as text frames, or binaryType not set to arraybuffer | Verify the bridge forwards binary frames; set ws.binaryType = 'arraybuffer' in browser paths | Reported once |
1008 DATA-0000 payload cannot be decoded | encoding or sample_rate did not match the actual audio, or first frame was lost | Match codec and sample rate exactly; verify raw versus containerized format | Reported once |
| Only one call leg produces transcripts | Two-channel audio sent without correct channel mapping; missing leg outside the WebSocket path | Split telephony audio correctly before streaming; inspect channel_index in transcript events | Reported once |
The encoding mismatch failure catches the most teams. Deepgram does not infer encoding from raw streams. If you send linear16 audio but tag the connection as mulaw, the stream closes immediately with a decode error. Check the format your telephony layer delivers before you write the connection parameters, not after you see the error. Teler streams at 8k sample rate by default, which matches Deepgram’s supported 8000 Hz rate, but you must still set encoding explicitly. The Teler WebSocket audio format is documented in the Teler API reference.
One honest limitation of using Teler with Deepgram: Teler’s default chunk size is 400 milliseconds, while Deepgram’s Flux model recommends 80-millisecond chunks for optimal turn detection. You need to configure Teler’s chunk size explicitly, or accept slightly coarser turn boundaries. Teler supports chunks as small as 20 milliseconds, so this is configurable, but it is not automatic.
Which integration approach fits your architecture?
| Approach | Where audio terminates | Latency cost | Operational burden | Lock-in |
|---|---|---|---|---|
| Direct WebSocket bridge (Teler to Deepgram) | Your application server | Lowest: one forwarding hop | Low: no media server to run | Deepgram STT only |
| SIP trunk into a media server | FreeSWITCH, Asterisk, or similar | Medium: extra transcoding hop | High: SIP call control per RFC 3261, RTP handling, codec negotiation | Media server vendor |
| Provider-native telephony media bridge | Carrier’s streaming feature | Medium: codec constraints from carrier | Low: carrier handles bridging | Carrier and STT vendor both |
| WebRTC gateway | Browser or app edge | Medium: signaling and jitter buffer per W3C WebRTC | Medium: signaling server, echo control, browser compatibility | WebRTC stack |
| Orchestration layer or voice-agent stack | Orchestration platform | Higher: abstraction adds hops | Low: platform manages routing | Platform vendor |
For most teams building AI phone agents in 2026, the direct WebSocket bridge is the right starting point. You own the forwarding loop, you control chunk size, and you avoid the transcoding hop that adds latency. The SIP trunk approach makes sense when you already run a media server for other reasons, such as call recording or DTMF handling, and adding a Deepgram output is incremental work. The WebRTC gateway path fits browser-originated calls where the source is a web app rather than a PSTN number. For more on how real-time audio streaming works across these approaches, see How To Handle Real-Time Audio Streams With Voice API Integration.
Frequently asked questions
What audio formats does the Deepgram API accept for live streaming?
Deepgram’s v2 listen endpoint accepts linear16, linear32, mulaw, alaw, opus, and ogg-opus for raw streams, plus containerized linear16 in WAV and opus in Ogg or WebM. Supported sample rates are 8000, 16000, 24000, 44100, and 48000 Hz. For raw streams, you must supply both encoding and sample_rate as connection parameters; Deepgram does not infer them. Containerized formats include enough header information for automatic detection.
Why does the Deepgram WebSocket close with 1011 NET-0002 during a call?
Deepgram closes the stream when no binary audio arrives within its timeout window. Sending KeepAlive messages alone does not prevent this. During silence on the call, send silent audio frames rather than pausing the stream. When the call ends, send sendCloseStream explicitly rather than dropping the socket.
Should I use a SIP trunk or a WebSocket bridge to connect calls to Deepgram?
Use a WebSocket bridge if you are starting fresh. A SIP trunk requires a media server to handle RTP per RFC 3550 and SIP call control per RFC 3261, which adds operational complexity and a transcoding hop. A WebSocket bridge, as Teler provides, delivers binary audio directly to your application server, which then forwards frames to Deepgram with no intermediate media server. Choose the SIP path only if you already run media server infrastructure for other reasons.
What happens if I start sending audio before the Deepgram socket is open?
Deepgram may treat the stream as corrupt and close it. This failure has been reported twice in GitHub discussions. The fix is to open the Deepgram connection first, buffer any audio that arrives before the OPEN event fires, and begin forwarding only after the socket is confirmed open. This is especially important when the telephony WebSocket and the Deepgram WebSocket open concurrently.
Does the Deepgram API handle multichannel call audio for both legs?
Deepgram supports multichannel audio, but you must split or map the telephony audio correctly before streaming. If you send two-channel audio and only one leg produces transcripts, inspect the channel_index field in transcript events to verify both channels are reaching Deepgram. The missing-leg issue is typically in the audio bridge, not in Deepgram’s processing.
What does Teler handle and what must I build myself?
Teler handles carrier connectivity, phone number provisioning in 50 or more countries, RTP-to-WebSocket bridging, barge-in and interruption handling, and live call control including hangup, mute, and DTMF. You build the forwarding loop from Teler’s WebSocket to Deepgram’s WebSocket, the transcript processing logic, and any downstream LLM or TTS integration. Deepgram’s STT billing is separate from Teler’s telephony billing; check current Teler pricing for the telephony layer.
How does the Deepgram API handle turn detection for live calls?
Deepgram’s Flux model family includes contextual turn detection with configurable parameters including eager_eot_threshold, eot_threshold, and eot_timeout_ms. These control how aggressively the model declares end-of-turn. The documentation does not specify default values for these parameters, so test them against your call audio before deploying. Tighter turn detection reduces response latency but increases the risk of cutting off a speaker mid-sentence.
What are the concurrency and rate limits for the Deepgram live listen endpoint?
Deepgram’s documentation does not publish specific concurrency caps, rate limits, or maximum session durations for the live listen endpoint in the sources available as of September 2026. Contact Deepgram directly or review your plan terms for production limits. For load testing guidance before a production launch, see How To Test Voice API Integration Before Production Launch.
What can you build now with the Deepgram API on live calls?
With the Deepgram API connected to a live call via a WebSocket bridge, you have real-time transcript events arriving in your application server within the latency of the audio path. The decision that matters most is where audio terminates: if you control that hop, you control the format, the chunk size, and the latency. If you rely on a carrier’s native streaming feature, you accept their codec constraints. For teams building AI phone agents in 2026, the Deepgram API paired with a telephony layer that exposes raw WebSocket audio is a practical starting point, but only if you treat the audio format parameters as first-class configuration, not defaults to revisit later. For a broader view of how speech-to-text fits into a full voice stack, see Top 7 Best Speech-to-Text API Providers in the US.