Skip to main content
The answer URL is an https URL you host. When a call starts on a leg, Unpod fetches it and runs the instructions you reply with, in XML or JSON. The format is the same for outbound and inbound calls.

When Unpod calls your answer URL

An inbound call is still ringing when Unpod fetches the URL, so a reply that starts with <Hangup/> rejects the call. On an outbound leg, Unpod runs your instructions once that person connects, instead of just bridging them:
The answer URL must be public https, or you get 400 invalid_url.
Outbound answer-URL requests are not signed yet, so a route that rejects unsigned requests rejects them too. Serve outbound legs on a separate route (the minimal server below uses /outbound), or skip verification for them until signing ships. After 5 failures in a row, Unpod skips your URL for 30 s and applies the fallback, and inbound and outbound share that limit - so don’t point outbound legs at the signed /answer route.

What Unpod sends

On an inbound call, your answer URL receives: Each StartApp also carries:
  • MPCUUID (ev.call_id): the id you use with the call APIs
  • CallUUID (ev.leg_id), From (ev.from_), To and Direction=inbound
  • SessionStart (ev.raw["SessionStart"])
On an outbound leg the request is also a StartApp, sent once the person answers. It carries the same fields, with the fixed values Direction=inbound and CallStatus=ringing, so don’t branch on Direction. Its CallUUID ends in -caller, so it doesn’t match the leg_id that create returned: match outbound requests on MPCUUID (ev.call_id). When the call ends, the same URL also gets a Hangup with Duration, StartTime, EndTime, AnswerTime and HangupCause; reply 200 to it. Inbound POST requests are signed once your org has a webhook secret (create it as in the Quick Start). Verify and parse them with parse_call_event - see Webhooks. Outbound requests are not signed yet, and GET requests are not signed (there’s no body to sign).

Reply with XML or JSON

Reply with Plivo-style XML or JSON. Greet the caller, then connect them to an agent:
Turn the caller away:
The first reply as JSON:
On <Hangup>, reason applies only when rejecting a ringing inbound call (busy sends 486, anything else 603). code is a SIP status 400-699 and works before and after answer. JSON field names differ from XML attributes: speak.text, play.url, wait.seconds, record.max_duration_secs. Only these verbs are supported. Any other element, such as <GetDigits>, <Conference> or <Redirect>, makes the whole reply fail and the fallback applies. Verbs run in order. When the last one finishes, Unpod hangs up; verbs after <Dial> don’t run. A reply can be at most 64 KB with 32 verbs, and 4096 characters in each <Speak>. Every audio URL must be public https. Set the voice and language on <Speak> itself:
Whether a call is recorded follows the recording policy (set by the Unpod team); record=True on create doesn’t override it today (see Recording). Don’t put <Record/> before <Dial>: it waits for the recording to finish (300 s by default) before dialing, and the caller hears silence.

A minimal answer server

app.py
Run it with python app.py and expose it as in the Quick Start. /answer rejects unsigned requests, greets every new inbound call and returns 200 for every other event. /outbound greets each outbound leg once it’s answered and acknowledges its Hangup. To route callers to free agents, see Incoming Calls.

Timeouts and fallback

Inbound. Set these on the number’s config (Incoming Calls): 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. Outbound. The timeout is fixed at 3000 ms. If your app fails or sends an empty reply, nothing runs and the leg stays connected until you end the call. A reply that starts with <Hangup/> ends it.

Serve a static file

No server needed: host a fixed XML file on any public https host, then pass answer_url="https://cdn.example.com/greeting.xml" and answer_method="GET" to create. Every call runs the same instructions, which suits Call Blasting.
greeting.xml
A GET sends the call’s fields as query parameters and is not signed. The URL must not redirect: Unpod doesn’t follow redirects.