Getting started
Quickstart
Run Offhook on your machine, talk to an agent in your browser, then place a real phone call.
What you need
- Python 3.13 and uv.
- Docker, to run Postgres.
- API keys from Deepgram, Cartesia and Groq. All three have free tiers.
- For phone calls only: a Plivo account with a number, and a way to expose your machine to the internet, such as ngrok.
1. Install and configure
git clone https://github.com/welldunai/offhook.git
cd offhook
uv venv && source .venv/bin/activate
uv pip install -e .
cp .env.example .env
In .env, set DEEPGRAM_API_KEY, CARTESIA_API_KEY,
GROQ_API_KEY and a master encryption key, which you can generate with:
python -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())"
# paste the output into .env as MASTER_ENCRYPTION_KEY 2. Start Postgres and create a dev tenant
docker compose up -d
alembic upgrade head
python -m app.admin seed-dev --voice-id 05a69c2d-457a-4802-8f91-628cb158b522 seed-dev creates a tenant, stores the provider keys from .env
as encrypted connections, creates a demo
agent, and prints two values:
- An API key, shown once. Export it so the examples below work as written:
export OFFHOOK_KEY="vk_dev_…" - A DEV_AGENT_ID. Add it to
.env.
3. Talk to the agent in your browser
python run.py
Open http://localhost:8000, allow the microphone and start talking.
The playground runs the same pipeline as a phone call over WebRTC, and shows live
transcripts and the latency of each stage.
4. Place a phone call
Phone calls need three more things:
- Carrier keys. Add
PLIVO_AUTH_ID,PLIVO_AUTH_TOKENandPLIVO_FROM_NUMBERto.envbefore runningseed-dev, so the demo agent gets a telephony connection. For Twilio, add a telephony connection through the API instead. - A public URL. The carrier calls back into Offhook when the call is answered. Run
ngrok http 8000and setPUBLIC_BASE_URLin.envto the https address it prints. - The worker. It dials queued calls and delivers webhooks. Run
python -m app.workerin a second terminal.
Restart python run.py so it picks up the new .env, then place the call:
curl -X POST http://localhost:8000/v1/calls \
-H "Authorization: Bearer $OFFHOOK_KEY" \
-H "Idempotency-Key: first-call" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "<DEV_AGENT_ID>",
"to_number": "+14155550123",
"context": { "patient_name": "Priya", "appointment_time": "Tuesday at 3 PM" }
}'{
"id": "8f2c4d1e-6b7a-4c2e-9f1d-3a5b7c9e1f20",
"status": "queued",
"direction": "outbound",
"provider_call_id": null
} 5. Get the result
Fetch the call, or subscribe to webhooks to be told when it changes. Once the call ends, it has an outcome, a transcript and a full event log.
curl http://localhost:8000/v1/calls/8f2c4d1e-6b7a-4c2e-9f1d-3a5b7c9e1f20 \
-H "Authorization: Bearer $OFFHOOK_KEY"{
"id": "8f2c4d1e-6b7a-4c2e-9f1d-3a5b7c9e1f20",
"status": "completed",
"direction": "outbound",
"to_number": "+14155550123",
"from_number": "+14155550100",
"provider_call_id": "abc-123",
"outcome": { "summary": "Confirmed for Tuesday at 3 PM" },
"error": null,
"error_code": null,
"has_recording": true,
"tags": {},
"created_at": "2026-10-15T10:00:00Z",
"started_at": "2026-10-15T10:00:04Z",
"ended_at": "2026-10-15T10:00:42Z"
} Next steps
- Connections and Agents: set up your own agent instead of the demo one.
- Calls: everything you can read about a call.
- Self-hosting: run Offhook in production.