pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog

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 (media events, μ-law at 8 kHz) becomes InputAudioRawFrames at the pipeline's input rate.
  • Bot audio is resampled to 8 kHz, μ-law encoded and sent as playAudio events.
  • Touch-tone digits become InputDTMFFrames.
  • An InterruptionFrame sends clearAudio.
  • An EndFrame or CancelFrame hangs up the call through Plivo's REST API (autoHangUp).

See How phone calls connect for the overall flow.

Installation

bun add @pipedog/serializer-plivo @pipedog/transport-websocket @pipedog/runner
import { 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_ID and PLIVO_AUTH_TOKEN.
  • The stream ID and call ID, which Plivo sends in its start message.

PlivoFrameSerializer

Configuration

Prop

Type

InputParams

params is a PlivoFrameSerializer.InputParams (PlivoFrameSerializerInputParams):

ParamTypeDefaultDescription
plivoSampleRatenumber8000Sample rate Plivo uses, in Hz.
sampleRatenumber | nullnullOverride for the pipeline input rate. null uses the pipeline's.
autoHangUpbooleantrueHang up the call on EndFrame or CancelFrame.
resamplerClearAfterSecsnumber | null0.2Seconds of silence after which the stream resamplers clear their history. null never clears.
ignoreRtviMessagesbooleantrueDrop RTVI messages instead of sending them to Plivo.

Usage

plivo.ts
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 autoHangUp on (the default), the constructor throws if callId, authId or authToken is missing. The hang-up is a DELETE of https://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 playAudio with contentType: "audio/x-mulaw" and the Plivo sample rate.
  • Set addWavHeader: false on the transport params.
  • An invalid DTMF digit is logged as a warning and ignored.

Runtime notes

RuntimeWorksWhat it needs
Bun✅ yesNothing.
Node.js✅ yesNothing.
Deno✅ yesNothing.
Cloudflare Workers✅ yesNothing.

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).

On this page