Skip to content

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.

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-modeinbound-caller-id-contextallowed-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 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 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.

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 match block 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, and match-on-call-details.
  • Modify operations are the same set as translations (replace, normalize, strip-prefix, regex-replace, …), with the same soft vs must- 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.

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.

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

Ingress, for a call arriving on a trunk:

  1. Caller-ID mode applied (fixed overwrite / modify inbound-context rewrite / provided pass-through).
  2. allowed-caller-ids screening against the post-rewrite value (fixed skips this — the value is yours).
  3. Special-number check, then the inbound translation walk.

Egress, for a call dialed out a trunk:

  1. outbound-caller-id-context rewrite, when set (no mode, no screening).
  2. 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.