Caller-ID Contexts
Caller-ID contexts apply the same match/modify engine used by translation contexts to the presented caller ID instead of the dialed number. Use them to normalize inbound caller ID to a canonical form, scrub malformed or short numbers, or rewrite outbound identity per policy.
A caller-ID context never chooses a destination — its rules have match and
modify blocks only, no target.
Where caller-ID contexts run
Section titled “Where caller-ID contexts run”Trunk profiles consume caller-ID contexts through two independent, per-direction hooks. Ingress and egress are separate policies with separate contexts — what you enforce on traffic from a trunk says nothing about calls delivered to it:
| Field | Direction | When it runs |
|---|---|---|
inbound-caller-id-context |
Ingress — calls arriving from the trunk | At call ingest, only when caller-id-mode is modify, before allowed-caller-ids screening |
outbound-caller-id-context |
Egress — calls dialed out the trunk | Just before the outbound dial, whenever set (no mode required) |
trunk-profiles: customer-pbx: caller-id-mode: modify inbound-caller-id-context: normalize-cid # what the customer asserts outbound-caller-id-context: delivery-format # what we present to them ...Namespace linkages carry the same directional knobs — a linkage is treated
as a virtual trunk, so a call transferring in from a peer namespace passes
the linkage’s caller-id-mode → inbound-caller-id-context →
allowed-caller-ids screen (with primary-caller-id for fixed mode), and
a call handed out to a peer passes its outbound-caller-id-context.
Everything on this page about trunk ingress and egress applies to linkage
entry and hand-off identically.
Ingress: trust and authorization
Section titled “Ingress: trust and authorization”Ingress caller-ID handling answers “what may this peer assert?” It has three parts, all applying only to calls arriving on the trunk:
caller-id-mode |
Behavior |
|---|---|
fixed |
The asserted value is discarded and replaced with the trunk’s primary-caller-id. No context, no screening — the value is yours, so it is trusted. |
provided |
The asserted value passes through unchanged (subject to allowed-caller-ID screening). |
modify |
The asserted value is walked through the inbound-caller-id-context, then screened. |
After the mode is applied, the trunk’s allowed-caller-ids list — exact
numbers or Asterisk-style patterns — screens the result. A caller ID outside
the list is rejected with the invalid-caller-id treatment rather than being
carried (and later signed) as a spoofed identity. An empty list means no
restriction; fixed skips screening entirely since the value is yours.
Setting an inbound-caller-id-context with a mode other than modify is a
validation error — the context would never run, which is almost always a
mistake rather than an intent.
Egress: presentation
Section titled “Egress: presentation”Egress handling answers “how should identity be formatted or branded on the
way out?” — it is a rewrite only, with no screening. When
outbound-caller-id-context is set, the caller ID the call is carrying is
walked through it immediately before the dial; the rewritten value is what is
presented to the far end, recorded on the CDR, and used for STIR/SHAKEN
signing.
caller-id-mode and allowed-caller-ids are never consulted at egress.
This is deliberate: at egress the caller ID legitimately belongs to whoever
originated the call — an arbitrary PSTN caller on DID delivery to a customer
trunk, or the original caller on a forwarded call. Identity authorization
happens where a call enters the switch (trunk ingress screening, and line
caller-ID enforcement for subscriber lines); by the time a call is leaving,
its identity has already been vetted.
Line-originated calls do not use caller-ID contexts at ingress: a line’s
caller ID comes from its configuration (caller-id, internal-caller-id,
caller-id-name), and the switch enforces it — unless the line profile sets
allow-unrestricted-caller-id, whatever the device asserts is ignored. A
line call leaving through a trunk is still subject to that trunk’s
outbound-caller-id-context like any other egress.
Structure and evaluation
Section titled “Structure and evaluation”Same shape as a translation context: priority keys (numeric strings, evaluated lowest first) mapping to rules.
caller-id-contexts: normalize-cid: "100": match: - ast-pattern: "_NXXNXXXXXX" modify: operations: - add-prefix: "+1" "200": match: - ast-pattern: "_1NXXNXXXXXX" modify: operations: - add-prefix: "+"Evaluation differs from translations in one important way:
- The first matching rule applies its modify and the walk stops. There are no “rewrite-and-continue” rules in a caller-ID context — exactly one rule (or none) fires per call.
- If no rule matches, the caller ID passes through unchanged.
- The
matchblock is evaluated against the caller ID, not the dialed number. All match operations are available —exact,regex,ast-pattern,and,always/never,time-of-day, andmatch-on-call-details. - Modify operations are the same set as translations (
replace,normalize,strip-prefix,regex-replace, …), with the same soft vsmust-semantics.
The same context can be referenced from both directions (and from multiple profiles) — direction comes from where it is referenced, not from the context itself.
Worked examples
Section titled “Worked examples”Normalize inbound caller ID to E.164 (trunk-served PBX)
Section titled “Normalize inbound caller ID to E.164 (trunk-served PBX)”Customer PBXs send caller ID in whatever format they were programmed with. Normalizing at ingress means everything downstream — CDRs, call-detail enrichment, screening on the far side of a transfer — sees one canonical form:
caller-id-contexts: normalize-cid: # 10-digit → +1XXXXXXXXXX "100": match: - ast-pattern: "_NXXNXXXXXX" modify: operations: - add-prefix: "+1" # 11-digit 1XXXXXXXXXX → +1XXXXXXXXXX "200": match: - ast-pattern: "_1NXXNXXXXXX" modify: operations: - add-prefix: "+" # Already E.164 → leave alone. "300": match: - regex: "^\\+" modify: operations: []
trunk-profiles: customer-pbx: caller-id-mode: modify inbound-caller-id-context: normalize-cid inbound-translation-context: from-customer ...Authorize a customer’s caller IDs (ingress anti-spoofing)
Section titled “Authorize a customer’s caller IDs (ingress anti-spoofing)”The number set a customer may originate with is enforced at ingress with the trunk’s allowlist, on the post-rewrite value:
trunk-profiles: customer-pbx: caller-id-mode: modify inbound-caller-id-context: normalize-cid ...
trunks: reseller-a: trunk-profile: customer-pbx allowed-caller-ids: - "_+1415555XXXX" # their assigned DID block, as a pattern ...A call asserting anything outside the block is refused with
invalid-caller-id. Because this is ingress-only, the same trunk’s inbound
DID delivery is unaffected — calls to the customer carry whatever caller ID
the calling party has.
Scrub unusable caller ID before an upstream (LEC egress)
Section titled “Scrub unusable caller ID before an upstream (LEC egress)”Your upstream rejects or flags calls with short or malformed caller IDs. On the egress trunk, pass through anything that looks like a real number and replace everything else:
caller-id-contexts: egress-scrub: # A full NANP or E.164 number passes through, normalized. "100": match: - regex: "^\\+?1?[2-9]\\d{9}$" modify: operations: - must-normalize: e164 # Anything else (extension-length, empty-ish, garbage) → main BTN. "200": match: - always: true modify: operations: - replace: "+14155551000"
trunk-profiles: upstream-out: outbound-caller-id-context: egress-scrub ...Note there is no caller-id-mode involved — the outbound context runs on
every call dialed out the trunk, regardless of the ingress trust settings.
Strict egress: refuse rather than scrub (wholesale)
Section titled “Strict egress: refuse rather than scrub (wholesale)”A wholesale operator that must never hand its upstream a call with unverifiable caller ID can make malformed CID a hard failure:
caller-id-contexts: egress-strict: "100": match: - always: true modify: failure-treatment: invalid-caller-id operations: - must-normalize: e164
trunk-profiles: upstream-out: outbound-caller-id-context: egress-strict ...A caller ID that can’t normalize to E.164 rejects the call with
invalid-caller-id instead of sending it. To also constrain which numbers
may appear, enforce allowed-caller-ids at ingress on the trunks the traffic
enters through — authorization belongs at the entry point, where the switch
knows whose identity is being asserted.
Time-based branding (egress)
Section titled “Time-based branding (egress)”Present the after-hours answering-service number as caller ID on calls delivered outside business hours (for callback routing on the far end):
caller-id-contexts: hours-branding: "100": match: - time-of-day: days: [mon, tue, wed, thu, fri] start: "08:00" end: "17:59" timezone: America/New_York modify: operations: [] # business hours: leave CID alone "200": match: - always: true modify: operations: - replace: "+18005550199"
trunk-profiles: branch-office-delivery: outbound-caller-id-context: hours-branding ...Interaction summary
Section titled “Interaction summary”Ingress, for a call arriving on a trunk:
- Caller-ID mode applied (
fixedoverwrite /modifyinbound-context rewrite /providedpass-through). allowed-caller-idsscreening against the post-rewrite value (fixedskips this — the value is yours).- Special-number check, then the inbound translation walk.
Egress, for a call dialed out a trunk:
outbound-caller-id-contextrewrite, when set (no mode, no screening).- The result is presented on the dial, recorded as the leg’s caller ID on the CDR, and used as the asserted identity for STIR/SHAKEN signing.
A call that crosses the switch trunk-to-trunk can therefore be rewritten independently at both edges: the entry trunk’s ingress policy vets the asserted identity, and the exit trunk’s egress context formats it for delivery.