Skip to main content
The end-to-end path: mint a key, configure a tenant, create an agent with a custom tool, attach a phone number, register a webhook, place a test call, and watch it live.

Prerequisites

  • A running voice server — see Overview for the Docker Compose stack
  • Twilio credentials and a phone number
  • An OpenAI key (and an ElevenLabs key if you want its TTS)
Everything below runs against https://api.voice.stateset.com/api/v1, or http://localhost:5050/api/v1 against a local server.

1. Mint a tenant API key

Tenant credentials are the unit of access. Every subsequent call uses one as a Bearer token. Create your first key in the console; after that, a key can mint further keys for itself:
Response
The key is returned once. Store it before moving on — it can be revoked and reissued, not retrieved.

2. Configure the tenant account

Set the tenant’s provider credentials and defaults — audio output provider, VAD tuning, and handoff workflow. See Configuration.

3. Create the agent

An agent version carries its prompt, model settings, and tools[]. Each tool is a JSON-schema function plus your HTTPS endpoint.
Unknown keys in config are rejected with a 400 rather than ignored, and a PATCH mints a new version that is not live unless you publish it. If an edit appears to have no effect, check which version is published before looking at the prompt.

How your tool endpoint is called

Mid-call, the server POSTs to your endpoint with:
  • Authorization passthrough
  • An X-Webhook-Signature HMAC over the raw body — the same scheme as webhooks
Your JSON reply becomes the tool result the model sees.
Verify that signature with the same helper you use for webhooks. One implementation covers both — see Verifying signatures.

4. Attach a phone number

Route a number to the agent with direction and agent_id. Inbound calls to that number now reach this agent.

5. Register a webhook for call results

Point an endpoint at the events you care about. For a first agent, voice.call.started, voice.call.ended, and voice.session.completed are enough — the last one carries persisted transcripts.

6. Place a test call

Test in the browser first — a web call runs the same pipeline with no number and no charges:
When that sounds right, dial for real:
This dials a real phone. DNC and TCPA quiet-hours checks apply, evaluated in the called party’s timezone, and a blocked call returns 403 rather than ringing. Send an Idempotency-Key derived from the thing you are calling about: the same key inside 24 hours replays the stored outcome instead of dialling again.

7. Read the transcript

If replies feel laggy, start at GET /call-logs/latency for p50/p95 rather than guessing — then drill into the individual call log.
See the full event catalog.

6. Place a test call

Either call the number, or place an outbound call with POST /make-call.
Faster still: POST /voice/web-calls gives you a browser call with no phone number needed, on the same pipeline. It’s the tightest loop while iterating on a prompt.
Send an Idempotency-Key on make-call — 24-hour replay protection means a retried request won’t place a second real call.

7. Inspect the session afterwards

Session records and transcripts are persisted. voice.session.completed delivers them to your webhook, and the session APIs let you fetch them directly.

8. Watch a live call

monitor listens; whisper speaks to the agent only; barge joins the caller; escalate hands to a human; end terminates.

Troubleshooting

Next

SDKs

TypeScript, Python, browser calls.

Configuration

Auth transport, providers, VAD, readiness.

Webhooks

Events and signature verification.

MCP server

Build and tune agents from Claude.
Last modified on August 29, 2026