The SIP reporter watches a practice's own signalling and translates what it sees into the call ingestion body. This page is the mapping, plus the part that surprises people: a SIP vantage point does not see the whole call, and the body has fields for saying so.
Everything here posts to the same endpoint with provider set to sip, which is also its default.
Mapping a dialog
| Ingestion field | Comes from |
|---|---|
providerCallId | The SIP Call-ID. |
occurredAt | When the reporter saw the message, on its own clock. |
call.from, call.to | The From and To URIs, split into number, name, extension and uri. |
call.trunk | The carrier trunk the INVITE arrived on, where the reporter knows it. |
call.diversion | The Diversion or History-Info chain, outermost first. |
raw | The message itself. Never parsed by us, kept for diagnosis. |
sequence | The reporter's own counter for the dialog. |
Numbers should be E.164 where the party has a real one. Normalisation happens server side, so a reporter that can only offer what the PBX said may offer exactly that.
What your vantage point can see
observer is the most consequential field on a SIP report, because it is the difference between "this call was not answered" and "we could not tell".
observer | Vantage point | Sees an answer elsewhere? |
|---|---|---|
endpoint | Registered as one extension, typically in a ring group. The default. | No. |
proxy | In the signalling path for the dialog. | Yes. |
pbx_api | The PBX's own event feed. | Yes. |
An endpoint in a ring group never learns that a human at another extension picked up. The PBX simply sends it a CANCEL. Read observer before concluding anything from a call with no call_answered event: on an endpoint report, that silence is not evidence of a missed call.
Set this honestly. A reporter that claims proxy from an endpoint's vantage point turns "we could not see" into "it did not happen", and the practice's reporting inherits the lie.
Reporting the end of a call
call_ended is where the honesty above gets expressed.
-
dispositionis one ofanswered,missed,busy,failed,cancelled,voicemailorunknown.unknownis a real answer and a ring-group endpoint will use it constantly. Admitting the reporter could not tell beats a guess that reporting will later average. -
answeredElsewhereis the one thing an endpoint does learn about a call it lost. RFC 3326 puts it on theCANCEL:The CANCEL that says somebody else took itReason: SIP;cause=200;text="Call completed elsewhere"Without that flag, answered-elsewhere and the caller giving up are the same silence.
-
sipCodeandsipReasoncarry the final response,486andBusy Here, for the failures worth keeping verbatim. -
durationis seconds from the call starting to it ending, ring time included.talkTimeis conversation only, and null means the reporter knows the total but not the split.
{
"type": "call_ended",
"provider": "sip",
"providerCallId": "a1b2c3@pbx.clinic.local",
"sequence": 2,
"occurredAt": "2026-09-04T14:32:19.700Z",
"observer": "endpoint",
"disposition": "unknown",
"duration": 14,
"talkTime": null,
"answeredElsewhere": true,
"sipCode": 200,
"sipReason": "Call completed elsewhere",
"call": {
"direction": "inbound",
"from": { "kind": "e164", "number": "+15550104242", "name": "DOLITTLE JANE" },
"to": { "kind": "extension", "extension": "101" }
}
}Callers who withhold their number
A caller sending Privacy: id gives the reporter nothing to store. That is a kind of caller, not a parse failure, and it must still be reported: it is the call the practice would most like a name on, and dropping it means no popup at all.
{
"type": "call_received",
"providerCallId": "d4e5f6@pbx.clinic.local",
"occurredAt": "2026-09-04T15:02:41.000Z",
"call": {
"direction": "inbound",
"from": { "kind": "anonymous", "number": null, "name": null },
"to": { "kind": "extension", "extension": "101" }
}
}Use unknown for the parties the reporter genuinely could not classify, and extension for internal ones.
Forwarded calls
The diversion chain is what stops a rolled call from looking like a direct one. A call that rang the main line, went unanswered and rolled to a mobile arrives with two hops, and without them it reads as somebody having dialled the mobile.
"diversion": [
{ "number": "+15550100000", "reason": "no-answer" },
{ "number": "+15550109999", "reason": "unconditional" }
]Events that repeat
call_hold is the event type that can legitimately happen more than once in a dialog, since one type covers both directions through released.
The server deduplicates on type:sequence when a reporter offers no providerEventId. So a SIP reporter has two workable options, and must pick one:
- Keep a monotonic
sequenceper dialog, incrementing on every event. This is the ordinary approach and needs noproviderEventIdat all. - Mint a
providerEventIdfor each event, if your counter cannot be trusted to be unique.
Sending two holds with the same sequence and no providerEventId means the second is filed as a duplicate of the first.
Where the reporter runs
The SIP reporter runs at the practice, translating on the client and posting this body directly. Its credential is a key federated to that organization and holding org.calls.events, created from the organization's manage page.
Because the reporter is inside the practice's network, it sees the signalling without any provider integration. That is the whole reason this vantage point exists, and it is also why observer defaults to the most limited of the three.