Plivo
Phone calls over Plivo audio streaming with PlivoFrameSerializer, used with FastAPIWebsocketTransport.
Overview
PlivoFrameSerializer speaks Plivo's audio streaming WebSocket protocol. Paired with
FastAPIWebsocketTransport, it lets a bot answer phone calls:
- Caller audio (
mediaevents, μ-law at 8 kHz) becomesInputAudioRawFrames at the pipeline's input rate. - Bot audio is resampled to 8 kHz, μ-law encoded and sent as
playAudioevents. - Touch-tone digits become
InputDTMFFrames. - An
InterruptionFramesendsclearAudio. - An
EndFrameorCancelFramehangs up the call through Plivo's REST API (autoHangUp).
See How phone calls connect for the overall flow.
Upstream page
Pipecat's documentation for the same serializer.
Source
packages/serializer-plivo/src/plivo.ts.
Plivo docs
The Stream XML element and audio streaming.
Installation
bun add @pipedog/serializer-plivo @pipedog/transport-websocket @pipedog/runnerimport { PlivoFrameSerializer } from "@pipedog/serializer-plivo/plivo";Prerequisites
- A Plivo account and a number whose answer XML has a bidirectional
<Stream bidirectional="true" keepCallAlive="true">wss://your.host/ws</Stream>. - Your auth ID and auth token, for auto hang-up. The examples read them from
PLIVO_AUTH_IDandPLIVO_AUTH_TOKEN. - The stream ID and call ID, which Plivo sends in its
startmessage.
PlivoFrameSerializer
Configuration
Prop
Type
InputParams
params is a PlivoFrameSerializer.InputParams (PlivoFrameSerializerInputParams):
| Param | Type | Default | Description |
|---|---|---|---|
plivoSampleRate | number | 8000 | Sample rate Plivo uses, in Hz. |
sampleRate | number | null | null | Override for the pipeline input rate. null uses the pipeline's. |
autoHangUp | boolean | true | Hang up the call on EndFrame or CancelFrame. |
resamplerClearAfterSecs | number | null | 0.2 | Seconds of silence after which the stream resamplers clear their history. null never clears. |
ignoreRtviMessages | boolean | true | Drop RTVI messages instead of sending them to Plivo. |
Usage
import { parseTelephonyWebsocket } from "@pipedog/runner/utils";
import { PlivoFrameSerializer } from "@pipedog/serializer-plivo/plivo";
import {
FastAPIWebsocketParams,
FastAPIWebsocketTransport,
} from "@pipedog/transport-websocket/fastapi";
import { websocketFetchHandler } from "@pipedog/transport-websocket/fetch";
import type { WebsocketConnection } from "@pipedog/transport-websocket/socket";
async function runCall(connection: WebsocketConnection): Promise<void> {
// Plivo's "start" message carries the stream and call IDs.
const [, callData] = await parseTelephonyWebsocket(connection);
const serializer = new PlivoFrameSerializer({
streamId: callData.streamId ?? "",
callId: callData.callId,
authId: process.env.PLIVO_AUTH_ID,
authToken: process.env.PLIVO_AUTH_TOKEN,
});
const transport = new FastAPIWebsocketTransport(
connection,
new FastAPIWebsocketParams({
audioInEnabled: true,
audioOutEnabled: true,
addWavHeader: false,
serializer,
}),
);
// ... build and run the pipeline with transport.input() and transport.output()
}
// The <Stream> in your Plivo XML points here.
const handler = websocketFetchHandler(runCall);Without auto hang-up, only the stream ID is needed:
const noHangUp = new PlivoFrameSerializer({
streamId: "stream-id",
params: new PlivoFrameSerializer.InputParams({ autoHangUp: false }),
});Notes
- With
autoHangUpon (the default), the constructor throws ifcallId,authIdorauthTokenis missing. The hang-up is aDELETEofhttps://api.plivo.com/v1/Account/{authId}/Call/{callId}/with Basic auth, attempted once. 204 and 404 (already ended) are logged at debug level; other failures are logged as errors and don't throw. - Outbound audio is
playAudiowithcontentType: "audio/x-mulaw"and the Plivo sample rate. - Set
addWavHeader: falseon the transport params. - An invalid DTMF digit is logged as a warning and ignored.
Runtime notes
| Runtime | Works | What it needs |
|---|---|---|
| Bun | ✅ yes | Nothing. |
| Node.js | ✅ yes | Nothing. |
| Deno | ✅ yes | Nothing. |
| Cloudflare Workers | ✅ yes | Nothing. |
Generated from pipecat.runtimes and pipecat.compat in packages/serializer-plivo/package.json: ✅ means the compat smoke test passes on that runtime.
Runs on Bun, Node, Deno and Cloudflare Workers (fetch for the hang-up, core's WASM resampler for
the audio). The package's golden tests replay vectors recorded from upstream: at 8 kHz the wire
output is byte-identical.
pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.
Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).