Three ways to answer
For your app to answer, attach the number with
attach_type="human_call" (see the
Quick Start). On a number attached to an
AI agent, the agent always answers: your answer URL can only reject the call (a reply that
starts with <Hangup/>), and no Hangup event reaches the answer URL.
Point your number at your app
Dashboard: open the number in the Unpod dashboard and set its answer URL. API:set_did sets the config for one number. In the URL, + becomes %2B. A PUT replaces
the number’s own config; fields you leave out come from its
trunk. get_did returns the config in effect. The number must be
one of your org’s numbers, otherwise 404.
Inbound configuration needs the Call API switched on for your org (see the
Quick Start): until then
set_did (or the PUT) returns
403 call_api_disabled. If the PUT returns a 5xx, Unpod has saved your config and
keeps retrying it in the background.InboundConfig fields
The webhook secret
Your org has one signing key for every signed request Unpod sends you: inbound answer-URL requests and status callbacks alike. Outbound answer-URL requests and recording events aren’t signed yet (see Which requests are signed). Create the key withwebhook_secret.rotate() (org owner or editor).
The full secret is shown only in that response, so store it as UNPOD_WEBHOOK_SECRET.
parse_call_event, as in the server below. See Webhooks for the
signature format and every event field.
Handle the call
Unpod sends your answer URL three events of its own. Reply toStartApp with instructions
(see Answer URL & XML for every verb) and return 200 for the
rest.
Point
status_callback_url at the same route, as in the config above. Unpod sends the
answer URL’s Hangup only once, but retries a status callback a few times over about a
minute (Delivery and retries), so a
missed Hangup usually arrives again. The route then also receives call events, such as
Incoming, Ringing, Answered and a Hangup per leg: return 200 for them. Hangup can
arrive more than once, which on_call.pop(ev.call_id, None) below handles. Every event
for a call carries the same ev.call_id; ev.leg_id differs per leg.
This server routes each caller to a free agent’s phone, or tells them everyone is busy:
app.py
python app.py and expose it as in the
Quick Start. After a valid signature it
always returns 2xx: Unpod treats any other status as a failure and may
fall back.
<Dial><Number> is a blind transfer: Unpod hands the call to that number and runs nothing
after it. If the chosen agent doesn’t pick up or is busy, Unpod doesn’t try the next agent.
Unpod answered the caller before dialing, so the answer URL’s Hangup reports
completed; the agent’s busy or no-answer cause comes on its leg’s Hangup status
callback (ev.hangup_cause).
Whether inbound calls are recorded follows the recording policy (set by the Unpod team),
not the number’s
record setting (see Recording). Don’t put
<Record/> before <Dial>: the caller waits in silence for the recording to finish
(300 s by default) before Unpod dials.When your server is down
Unpod waitsanswer_timeout_ms (500-10000, default 3000) for your reply. If your app doesn’t
answer, Unpod applies the number’s answer_fallback:
The fallback applies on a timeout, a connection error, any non-2xx status (including your
own 401), a reply that doesn’t parse, or a reply containing an action URL that isn’t public
https. An empty 2xx body,
<Response/> or {"actions": []} is not a failure: Unpod
continues with static routing.
After 5 failures in a row, Unpod stops calling the answer URL for 30 s and applies the
fallback straight away.
For a whole SIP trunk
Your carrier’s numbers reach Unpod over a SIP trunk. Set the same config on each number of the trunk withset_did:
set_did returns 404 for a number that isn’t one of your org’s. The numbers mapped to a
trunk are in origin_endpoint.dids of the
attach response.
inbound.set_trunk(name, ...) / PUT /sbc/v1/trunks/{name}/inbound set one config for a
whole trunk, but name is the trunk’s internal Unpod name, which the trunk API doesn’t
return yet. The name you gave the trunk (for example my-trunk) returns 404. Use
set_did per number until then.