Vapi

Serve Speechify voices to Vapi custom voice via the open-source tts-shims Vapi provider.

Overview

Vapi custom voice calls a webhook you host and expects raw PCM back. The tts-shims repo ships a vapi provider — a small Go binary that answers Vapi’s POST with Content-Type: application/octet-stream mono 16-bit little-endian PCM at the requested sample rate, translating the request into a Speechify stream call under the hood.

Your SPEECHIFY_API_KEY lives on the shim server; Vapi never sees it.

SpeechifyAI Agents — our own voice-agent platform

SpeechifyAI ships its own voice-agents platform powered by Simba 3.2, which ranks #1 on the Artificial Analysis TTS leaderboard as of July 2026. See SpeechifyAI Agents if you want an end-to-end voice-agent stack instead of using Vapi custom voice.

Why a Vapi-specific shim?

Vapi custom TTS doesn’t send an OpenAI-shaped body. It posts a nested message object:

1{
2 "message": {
3 "type": "voice-request",
4 "text": "The text the assistant wants to say.",
5 "sampleRate": 24000,
6 "timestamp": 1720000000000,
7 "call": {},
8 "assistant": {}
9 }
10}

The response contract is equally specific: raw PCM, mono, signed 16-bit little-endian, sample rate matching message.sampleRate, no WAV header. No OpenAI-shaped shim can serve this — hence the dedicated vapi provider.

Prerequisites

  • Speechify API key
  • Vapi account with a custom-voice-capable plan
  • Public HTTPS URL for the shim (Vapi calls it from its cloud)
  • Go toolchain locally (only to build the shim)

Build and run the shim

$git clone https://github.com/Speechify-AI/tts-shims
$cd tts-shims
$make vapi

Start it with your Speechify key in the environment:

$export SPEECHIFY_API_KEY=sk_your_key_here
$export SHIM_ADDR=:8772
$export SHIM_DEFAULT_MODEL=simba-3.2
$export VAPI_SECRET=replace_with_a_random_secret
$
$./bin/vapi

The provider mounts one route: POST /synthesize. Defaults: voice geffen_32, model simba-3.2.

Verify without a Vapi account

The custom TTS contract is HTTP — you can prove the whole path locally by sending the exact body Vapi sends:

$curl -s -o shim-smoke.pcm -w "%{http_code} %{size_download} %{content_type}\n" \
> -X POST "http://localhost:8772/synthesize" \
> -H "Content-Type: application/json" \
> -H "X-VAPI-SECRET: replace_with_a_random_secret" \
> -d '{"message":{"type":"voice-request","text":"Vapi custom voice, now speaking with Speechify.","sampleRate":24000,"timestamp":1720000000000,"call":{},"assistant":{}}}'

A working shim returns:

200 <byte-count> application/octet-stream

Wrap it as WAV locally to spot-check:

$ffmpeg -f s16le -ar 24000 -ac 1 -i shim-smoke.pcm shim-smoke.wav

Configure Vapi custom voice

In your Vapi assistant config:

1{
2 "voice": {
3 "provider": "custom-voice",
4 "server": {
5 "url": "https://your-shim-host.example/synthesize",
6 "secret": "replace_with_a_random_secret"
7 },
8 "timeoutSeconds": 30
9 }
10}

Vapi sends the server.secret value as X-VAPI-SECRET; the shim validates that header before calling Speechify. Vapi also supports custom headers and credentialId flows if that’s your credential-management style.

Sample rate matters

Vapi’s documented custom TTS sample rates are 8000, 16000, 22050, and 24000. The response must match message.sampleRate exactly. The shim validates the incoming rate and maps it to Speechify’s stream output format.

The reference test above uses 24000. If you deploy for telephony (8000 / 16000), test with the actual rate before sending live calls through it — sample-rate mismatches surface as choppy or noisy audio, not clean errors.

Never return WAV

Vapi’s contract is raw PCM with Content-Type: application/octet-stream. Do not return a WAV file, MP3, or any container-wrapped audio. The demo saves a local .wav only after the response is written to disk, for humans and tools to spot-check — that’s not what Vapi receives.

Production deployment

  • Put the shim behind HTTPS and a service host that holds environment variables (Cloud Run, Fly, Render, ECS — anywhere).
  • Set SPEECHIFY_API_KEY, SHIM_ADDR, SHIM_DEFAULT_MODEL, and VAPI_SECRET.
  • Rotate VAPI_SECRET and your Speechify key on a normal schedule — the shim doesn’t cache either.
  • Add health check monitoring on GET /healthz.

Resources