Any PBX: the iPhone wake webhook
If your phone system can send one HTTPS POST when a call arrives, it can ring a sleeping iPhone. This page is the whole contract: FreeSWITCH, Kamailio, or anything else with webhook or scripting support.
The required call flow
- A call arrives for the extension. Hold it: don't deliver the INVITE yet.
- POST the webhook below.
- Wait about 1.5 seconds. Apple wakes the phone, the app rings it via CallKit and refreshes its SIP registration.
- Deliver the INVITE to the extension as normal, with your usual timeout and voicemail fallback.
The request
POST https://sipreg.saffronsuite.com/v1/incoming-call
Authorization: Bearer pk_your_key_from_the_app
Content-Type: application/json
{
"call_id": "pbx-call-1700000000.12",
"to": "sip:101@pbx.example.com",
"from": "+61290001234",
"display_name": "Jane Caller"
}The pk_ key comes from the app: Settings → Incoming Calls. It's scoped to one account and can be rotated there; treat it like a password. Requests must be POST, HTTPS, JSON, and under 8 KB.
| Field | Required | Notes |
|---|---|---|
| call_id | yes | Unique per call, stable across retries (it's the idempotency key). The inbound leg's SIP Call-ID is ideal; any unique string works. Max 200 chars. |
| to | yes | The full SIP address of the extension being called, as sip:user@host. This is how the relay finds the phone: it must equal what the app registered. A bare extension number is rejected. Max 512 chars. |
| from | yes | The caller's number or extension. Shown as the caller on the iPhone. Max 128 chars. |
| display_name | no | The caller's name, shown on the incoming-call screen. Max 120 chars. |
The responses
| Code | Body | Meaning |
|---|---|---|
| 202 | accepted | Push sent. devices_woken says to how many phones. |
| 202 | duplicate_pending | Retry of a call_id that's already in flight. Harmless. |
| 200 | duplicate_accepted | Retry of a call_id that already woke the phone. Harmless. |
| 401 | invalid_api_key | Missing or wrong pk_ key. Re-copy it from the app. |
| 402 | subscription_required | No active subscription on the account. Subscribe in the app. |
| 404 | no_active_devices | No enrolled phone matches the 'to' address. Almost always an AOR mismatch. |
| 409 | idempotency_conflict | Same call_id, different payload. Keep the payload identical on retries. |
| 422 | invalid_payload | A field failed validation; the issues array says which. |
| 502 | apns_failed | Apple couldn't deliver the push to any device. Usually transient; retry. |
Retrying with the same call_id and payload is always safe; the relay is idempotent per call.
Test it from a terminal
You don't need a PBX to test the wake path. From any machine, with the values your app is actually configured with:
curl -s https://sipreg.saffronsuite.com/v1/incoming-call \
-H "Authorization: Bearer pk_your_key_from_the_app" \
-H "Content-Type: application/json" \
-d '{"call_id":"test-1","to":"sip:101@pbx.example.com","from":"100","display_name":"Test"}'Lock the phone first, for the full effect. 202 and a ring means the wake path works; anything else is decoded on the troubleshooting page. Once the wake works, hooking it into your PBX is just running that POST at the right moment in your call flow (FreeSWITCH: mod_curl or a Lua hook; Kamailio: http_client before relaying the INVITE).