Inbound receptionist — end-to-end
Complete recipe — register your Twilio number with an agent, handle incoming calls, react to lifecycle events.
A production-ready inbound flow. Customer dials your number → zoxaAI bridges to your agent → you get lifecycle webhooks and full transcripts for every call.
Unlike outbound, you register the number once. After that, every inbound call to that number is automatic.
Create the agent
curl -X POST https://dashboard.zoxa.ai/api/v1/agents \
-H "X-API-Key: zsk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Inbound receptionist",
"systemPrompt": "You are the first-line agent for Acme. Greet warmly, ask what they need, and route them via the transferCall tool to the right team. Never put callers on hold for more than 30 seconds.",
"greeting": { "firstMessages": ["Hello, this is Acme — how can I help you today?"] },
"llm": { "provider": "openai", "model": "gpt-5.4-mini", "temperature": 0.7 },
"tts": { "provider": "elevenlabs" },
"stt": { "provider": "soniox", "interruptionMinWords": 2 },
"tools": [
{ "type": "transferCall", "name": "transfer_to_team", "description": "Transfer the caller to the right team.", "config": { "destination": "+14155559999" } },
{ "type": "endCall", "name": "end_call", "config": { "messageType": "custom", "customMessages": ["Thanks for calling Acme!"] } }
],
"maxCallDurationS": 600,
"webhook": { "url": "https://your.app/zoxa/webhook" }
}'Register the number for inbound routing
This is the one-time call that wires zoxaAI into your Twilio (or Vobiz) number.
curl -X POST https://dashboard.zoxa.ai/api/v1/call \
-H "X-API-Key: zsk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "inbound",
"callConfig": {
"provider": "twilio",
"phoneNumber": "+14155550100",
"auth": { "accountSid": "AC...", "authToken": "..." }
},
"agentId": "<agent.uuid>"
}'Response:
{
"bindingId": 5,
"phoneNumber": "+14155550100",
"provider": "twilio",
"webhookUrl": "https://dashboard.zoxa.ai/api/v1/telephony/run",
"status": "registered"
}zoxaAI has now PATCHed the Twilio IncomingPhoneNumber.VoiceUrl to point at our webhook. Any call to +14155550100 from now on bridges to your agent.
Re-POSTing is safe
The same call is idempotent and last-write-wins on (provider, phoneNumber). Re-POST any time you want to swap the agent or credentials — no duplicate bindings.
Verify it works
Dial +14155550100 from any phone. You should hear the agent's greeting line within ~1 second of the line connecting.
If you want to confirm via API:
# List inbound calls in the last 5 minutes
FIVE_MIN_AGO=$(date -u -v-5M +%Y-%m-%dT%H:%M:%SZ)
curl "https://dashboard.zoxa.ai/api/v1/calls?call_source=api_inbound&from_date=$FIVE_MIN_AGO" \
-H "X-API-Key: zsk_..."Handle lifecycle webhooks
Identical handler to outbound — see the outbound flow webhook handler. call.ended tells you who called, when, and why it ended — but no transcript. Transcript / recording / summary land on the call.completed event that fires after post-processing. Full payload reference: Webhook events.
What's different from outbound
| Outbound | Inbound | |
|---|---|---|
| You initiate the call | ✓ | ✗ (customer does) |
| Credentials sent per call | ✓ | ✗ (stored on the binding once) |
| Idempotent? | ✗ — each POST /call places a new dial | ✓ — re-POST overwrites the binding atomically |
| Lifecycle webhook per call | ✓ | ✓ |
Managing the binding
GET /api/v1/telephony/inbound/bindings # list registered numbers
GET /api/v1/telephony/inbound/bindings/{id} # detail
PATCH /api/v1/telephony/inbound/bindings/{id} # swap agent without re-provisioning the provider
DELETE /api/v1/telephony/inbound/bindings/{id} # deprovision + removePATCH is cheap — perfect for "swap to a different agent for the holiday season" without re-touching Twilio. See Inbound bindings — update.
Related
- Calls — inbound — the endpoint reference
- Inbound bindings — manage registered numbers
- Example agents — inbound receptionist + multilingual variants