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.

Why

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:

Configure once, the bridge resolves the right combination on every outbound INVITE.

Which header does my carrier want?

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.

HeaderWhat it does
FromStandard 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 recommendedP-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.
PPIP-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.
RPIDRemote-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.
PrivacyPrivacy: 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 destinationHeaders 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 CMFrom + RPID. PAI is supported in 12.5+ but RPID is still the lowest-friction option.
3CXFrom + 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.
Don’t assume the carrier will trust you. RFC 3325 PAI is only meaningful inside a trust domain. A carrier that doesn’t know you may discard PAI entirely or replace it with one it minted from your auth credentials. Always test with a real call to a phone you control before relying on PAI for billing or CLI display.
Step 1

Set the per-user identity

Open register.html (admin area), edit a user, scroll to the SIP identity section. Six optional fields:

From display nameFree-form display name. Appears as the human-readable caller-ID on standard SIP clients. Example: Stefan Engelbert.
From URI user-partThe 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-IdentityFully-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-IdentitySame shape as PAI. Set when the carrier explicitly asks for PPI; rarely needed alongside PAI.
Remote-Party-IDSame shape as PAI. Set when targeting an older PBX that doesn’t read PAI.
PrivacyDefaults 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.

Tenant default (optional). Want every user to get the same baseline identity — e.g. one corporate PAI for users who haven’t set their own? Add a 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.
Step 2

Set the per-trunk policy

Open trunks-admin.html, expand a trunk, find the Outbound identity headers row.

FromAlways emitted (the toggle is informational). Display name and URI come from the user override → tenant default → trunk default chain.
PAICheck to emit P-Asserted-Identity. Default off on new trunks — opt in once you have confirmed the carrier honours it.
PPICheck to emit P-Preferred-Identity. Skip unless the carrier explicitly asks.
RPIDCheck to emit legacy Remote-Party-ID. Use for Asterisk < 13 and Cisco-UCM destinations.
Privacy defaultDefault 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.

Step 3

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”).

Step 4

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:

noneNo Privacy: header emitted. CLI is shown to the called party. Default.
idStrip 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.
headerStrip headers that contain user-related information beyond identity. Rarely used in practice; some SBCs treat it as a superset of id.
sessionStrip information at the session level (User-Agent, Server, etc.). Useful when the called party shouldn’t see your SIP stack version.
userStrip user-provided headers; carrier-injected ones remain. Niche.
criticalDrop 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.

Step 5

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.

Troubleshooting

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:

  1. The user actually has a sip_identity.from_user or pai set — re-open the user in register.html and look at the SIP identity section.
  2. The trunk policy has the relevant header checked (PAI for most carriers, RPID for older PBXes).
  3. 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.

Need a deeper look? The bridge logs the resolved identity on every outbound INVITE: search for 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.