welldun. offhook

Self-hosting

Self-hosting

Offhook is a backend you run on your own infrastructure: an API, a worker and Postgres, plus an optional dashboard. This page explains what each piece does. Deploying and Configuration cover the steps.

What you run

ComponentCommandWhat it does
APIrequired python run.py Serves the /v1 API, the carrier webhooks and the call audio stream. Keeps no state of its own, so you can run several behind a load balancer.
Workerrequired python -m app.worker Dials queued calls and delivers webhooks. Without it, calls stay queued. You can run several; each job is claimed by exactly one worker.
Postgresrequired postgres:16 The only datastore. Tenants, encrypted connections, agents, calls, the job queue, usage and the event log all live here. No Redis or message broker needed.
Dashboardoptional static site in dashboard/ A web console for your calls, agents and keys. Build it to static files and host it anywhere. Sign-in uses Clerk.

How a call flows

Calls are asynchronous, which is why the worker is required:

Flow
client ──POST /v1/calls──▶  API  ──enqueue──▶  Postgres (call_jobs)
                                                   │
                            worker ──claim job────▶─┘
                              │
                              ├─ decrypt the tenant's connection secrets
                              ├─ dial through the carrier (Plivo or Twilio)
                              └─ carrier answers ──▶ webhook to PUBLIC_BASE_URL
                                                       │
                          API audio stream (WebSocket) ◀┘
                              │
                              └─ STT ─▶ LLM ─▶ TTS  (one pipeline per call)

This has two consequences for your setup. The worker needs outbound internet access to reach the carrier and the AI providers. And PUBLIC_BASE_URL must be an HTTPS address the carrier can reach from the internet, because the carrier calls back into the API when the call is answered.

What you need

  • Python 3.13 where the API and worker run, or one container image for both.
  • Postgres that both can reach. The project is tested with Postgres 16.
  • A master encryption key: 32 random bytes, base64-encoded, kept outside the database. If you lose it, every stored provider key is unrecoverable. Keep it in a secrets manager, or use AWS KMS.
  • A public HTTPS address for the API, if you place real phone calls.
  • Accounts with your providers: Deepgram, Cartesia, a language model and a carrier. Their keys go into each tenant's connections, not into server configuration.

Ready? Continue to Deploying.