SIP identity · outbound caller-ID
Outbound calls signed with the caller’s own identity
Per-user From, P-Asserted-Identity, P-Preferred-Identity, Remote-Party-ID and RFC 3323 Privacy. Configure once per user, decide per trunk which headers to put on the wire — carriers that honour PAI see the calling person, carriers that don’t see the trunk’s service number, and your CLIR-only routes stay anonymous.
One trunk, many callers
A typical CodeB deployment has one or two SIP trunks shared by everyone in the company. By default that means every outbound call shows the trunk’s username in the From header and looks like it came from the company, not from the person dialling. For inbound routing on the far side, for call-back, and for trust signals on carrier-class trunks, that’s rarely what you want.
CodeB’s answer is two layers:
- Per-user identity — each registered user can carry a display name, a From URI user-part, and explicit PAI / PPI / RPID URIs in their profile. Stored in
sip_identityon the user record. - Per-trunk policy — each trunk decides which of those headers to actually emit on the wire, plus a default
Privacy:value (RFC 3323) for restricted-CLI routes.
Configure once, the bridge resolves the right combination on every outbound INVITE.
Choosing PAI vs PPI vs RPID
Different carriers and PBXes honour different headers. Pick whichever the receiving side documents. When in doubt, ask the carrier or send a test call and compare what arrives. The table below collects what we have tested — it is not a promise of universal compatibility.
| Header | What it does |
|---|---|
| From | Standard SIP From header. Always emitted — this is the visible caller-ID for clients that don’t inspect P-headers. CodeB lets you override its display name and user-part per user; if nothing is set, the trunk default is used. |
| PAI recommended | P-Asserted-Identity (RFC 3325). Trusted-identity header for carrier-class trunks. The receiving side is meant to take this as the network-asserted caller-ID, even if From is something else. Wholesale carriers and most BYOC providers prefer PAI. |
| PPI | P-Preferred-Identity (RFC 3325). Used when the sender is asking the trust boundary to choose this identity; the proxy may then synthesise PAI on its side. Rarely needed alongside PAI — typically you ship one or the other. |
| RPID | Remote-Party-ID (legacy, pre-PAI draft). Some older PBXes (Asterisk before 13, Cisco UCM in particular) still honour RPID instead of PAI. CodeB emits it with party=calling;screen=yes. |
| Privacy | Privacy: header (RFC 3323). One of none, id, header, session, user, critical. Tells the next hop to strip identifying headers (id) or rewrite session-level identifiers. none is the default; id is what you set for blanket CLIR. |
Quick reference by destination
| Tested destination | Headers to enable |
|---|---|
| FRITZ!Box (UPnP / fonctions) | From only. The FRITZ!Box passes From through verbatim to the upstream trunk; PAI is ignored at the box and may confuse some upstream carriers. |
| Asterisk 13+ (chan_pjsip) | From + PAI. PJSIP reads PAI as ${CALLERID(num)} when trust_id_inbound is set on the endpoint. |
| Asterisk < 13 (chan_sip) | From + RPID. chan_sip never grew first-class PAI support. |
| Cisco Unified CM | From + RPID. PAI is supported in 12.5+ but RPID is still the lowest-friction option. |
| 3CX | From + PAI. 3CX honours PAI on inbound trunks by default. |
| Wholesale BYOC (most) | From + PAI. Test with one call and check what shows up at the destination. Some carriers require pre-authorised PAI URIs — ask before going live. |
Set the per-user identity
Open register.html (admin area), edit a user, scroll to the SIP identity section. Six optional fields:
| From display name | Free-form display name. Appears as the human-readable caller-ID on standard SIP clients. Example: Stefan Engelbert. |
|---|---|
| From URI user-part | The user-part of the From URI — what goes before @. Most carriers expect an E.164 number here. Example: +35679567034. Leave blank to fall back to the trunk username. |
| P-Asserted-Identity | Fully-formed SIP URI, no angle brackets. Example: sip:+35679567034@phone.codeb.io. Carriers may require a specific domain or a tel: URI — ask first. |
| P-Preferred-Identity | Same shape as PAI. Set when the carrier explicitly asks for PPI; rarely needed alongside PAI. |
| Remote-Party-ID | Same shape as PAI. Set when targeting an older PBX that doesn’t read PAI. |
| Privacy | Defaults to inherit trunk default. Set explicitly when this user’s calls should always go out anonymous (id) or always non-anonymous (none), regardless of trunk policy. |
Empty fields inherit the trunk default. The page saves to sip_identity under the user’s profile in the per-tenant credentials JSON. No restart required — the bridge re-reads identity per call.
WebPhone:SipIdentityDefault key in your tenant’s App_Data/<domain>/appsettings.json with a JSON object of the same six fields. Per-user overrides win; missing fields fall through to the tenant default; missing tenant defaults fall through to the trunk.
Set the per-trunk policy
Open trunks-admin.html, expand a trunk, find the Outbound identity headers row.
| From | Always emitted (the toggle is informational). Display name and URI come from the user override → tenant default → trunk default chain. |
|---|---|
| PAI | Check to emit P-Asserted-Identity. Default off on new trunks — opt in once you have confirmed the carrier honours it. |
| PPI | Check to emit P-Preferred-Identity. Skip unless the carrier explicitly asks. |
| RPID | Check to emit legacy Remote-Party-ID. Use for Asterisk < 13 and Cisco-UCM destinations. |
| Privacy default | Default Privacy: value for calls on this trunk. none is the default (non-anonymous). Set to id for blanket CLIR on a dedicated outbound trunk. |
Save. The bridge picks up the new policy on the next dial — no service restart.
What the wire looks like
Take a user with from_display = "Stefan", from_user = "+4915157610183", pai = "sip:+4915157610183@phone.codeb.io", privacy = none, dialling out through a trunk with IdentityHeaders = [From, PAI] and PrivacyDefault = none. The outbound INVITE carries:
; standard SIP request line + Via … omitted for brevity From: "Stefan" <sip:+4915157610183@trunk.example.com>;tag=… To: <sip:+35679567034@trunk.example.com> Call-ID: … CSeq: 1 INVITE Contact: <sip:bridge@192.168.0.20:5070> P-Asserted-Identity: <sip:+4915157610183@phone.codeb.io> Content-Type: application/sdp Content-Length: …
Switch the trunk policy to IdentityHeaders = [From, PAI, RPID], PrivacyDefault = id, leave the user identity as above — the bridge now emits both:
From: "Stefan" <sip:+4915157610183@trunk.example.com>;tag=… P-Asserted-Identity: <sip:+4915157610183@phone.codeb.io> Remote-Party-ID: <sip:+4915157610183@phone.codeb.io>;party=calling;screen=yes Privacy: id
A downstream carrier that reads PAI + Privacy correctly will pass the call through anonymously, but log the real identity in its records for traceback (id means “strip identity from displayed CLI, keep it in the network”).
What Privacy actually does
RFC 3323 defines five privacy tokens plus none. The bridge ships whatever string you set; the next hop decides what to do with it. In practice:
| none | No Privacy: header emitted. CLI is shown to the called party. Default. |
|---|---|
| id | Strip the displayed caller identity. The carrier replaces PAI / From with Anonymous or similar at the boundary, but keeps the real identity inside its network. This is what most operators mean by CLIR. |
| header | Strip headers that contain user-related information beyond identity. Rarely used in practice; some SBCs treat it as a superset of id. |
| session | Strip information at the session level (User-Agent, Server, etc.). Useful when the called party shouldn’t see your SIP stack version. |
| user | Strip user-provided headers; carrier-injected ones remain. Niche. |
| critical | Drop the call rather than forward it without the requested privacy. Use only when failing closed is what you want. |
Most deployments use none by default and switch one specific trunk to id for anonymous outbound use cases.
OIDC sip_* claims (for RPs)
Relying-party applications that sign in users via CodeB’s OIDC provider can read the same identity from /userinfo as flat scalar claims, gated on the standard profile scope.
// GET /oidc.ashx?action=userinfo with Bearer access token // issued for `scope=openid profile` { "sub": "stefan", "preferred_username": "stefan", "name": "Stefan Engelbert", "email": "stefan@example.com", "sip_from_display": "Stefan Engelbert", "sip_from_user": "+4915157610183", "sip_pai": "sip:+4915157610183@phone.codeb.io", "sip_privacy": "none" }
Empty fields are omitted from the response — only the keys the user actually set come back. The same claims also appear in id_tokens when profile is requested.
This lets Nextcloud, WordPress, or any in-house tool that already reads CodeB OIDC tokens display or use the user’s authoritative caller-ID without a second API hop.
If outbound calls don’t carry what you expect
The bridge log shows identityHeaders=From even though I checked PAI
The trunk-side change in trunks-admin.html didn’t save. Reload the page and re-open the trunk — if the PAI checkbox is empty again, the save endpoint rejected the payload (look for the inline error). A common cause is an unrelated required field (Host, Username) being blank on a row added by mistake.
The carrier still shows my trunk number, not the user’s
Three things to check, in order:
- The user actually has a
sip_identity.from_userorpaiset — re-open the user inregister.htmland look at the SIP identity section. - The trunk policy has the relevant header checked (
PAIfor most carriers,RPIDfor older PBXes). - The carrier honours the header. Many carriers discard PAI from non-trusted sources and substitute their own based on your auth credentials — nothing you put in PAI will reach the called party. Confirm with the carrier’s support team before assuming a configuration bug.
I want the user’s name on CLI but a corporate number on CLI-number
Set from_display = "Stefan Engelbert" (the user’s name) and from_user = "+35679567034" (the corporate switchboard number). The From header becomes "Stefan Engelbert" <sip:+35679567034@trunk-host> — the display name is the person, the dialable number is the company. Most consumer phones show display name when it’s present, number when it isn’t.
How do I make ONE specific outbound trunk always anonymous?
In trunks-admin.html, set that trunk’s Privacy default = id. Every call on that trunk goes out with Privacy: id. Per-user identity is still passed in PAI inside the trust domain (so the carrier’s records keep the real caller for traceback), but the called party sees Anonymous. Route the user to that trunk for anonymous calls; route them to a different trunk for non-anonymous calls.
The carrier wants a tel: URI not a sip: URI in PAI
Some wholesale providers (notably for STIR/SHAKEN attestation) want PAI as a tel: URI. Set pai = "tel:+4915157610183" on the user. CodeB ships the value verbatim — no carrier-specific transformation. If your carrier wants both, you can put the tel: URI in pai and a sip: URI in ppi.
The header arrives but the displayed CLI is still the trunk default
The header probably arrives at the carrier but the carrier’s far-end equipment doesn’t read it. Two next steps: (1) ask the carrier which header they trust on inbound — some explicitly do PAI, some explicitly do RPID, some take From only when authenticated. (2) test from a phone you control on the same carrier — if the same headers show up but display differs, it’s the called handset, not the route.
INVITE with From display= and you’ll see identityHeaders=… and privacy=… next to the call. The full SIP trace for an outbound call is one line below that.