Skip to content

Trunk Profiles

A trunk profile bundles the behavior shared by a class of trunks: which translation contexts calls enter and leave through, how much of the peer’s asserted caller ID is trusted, STIR/SHAKEN handling, dial timers, URI selection, and codec policy. Define one profile per kind of interconnect — carrier ingress, carrier egress, customer PBX — and point each trunk at the right one.

trunk-profiles:
customer-pbx-profile:
inbound-translation-context: from-customers
outbound-translation-context: to-customers
caller-id-mode: provided
attestation-mode: A
resign-policy: resign
uri-selection: sequential
ring-for: 30
codecs: [ulaw, g722]
Field Required Meaning
inbound-translation-context yes Translation context for calls arriving on these trunks (DID delivery).
outbound-translation-context yes Translation context for calls being sent out these trunks.
caller-id-mode yes Ingress caller-ID trust: fixed, provided, or modify (below).
inbound-caller-id-context with mode modify Caller-ID context run at ingress. Only valid alongside modify.
outbound-caller-id-context no Caller-ID context applied when dialing out these trunks. Independent of mode.
special-number-profile no Which special-number profile intercepts emergency numbers arriving on these trunks. Empty = default.
attestation-mode when the namespace signs Ingress: the STIR/SHAKEN attestation level (A, B, C) your namespace asserts when it signs a call that arrived on these trunks (below). Ignored without a stirshaken policy block.
resign-policy no Ingress: what happens to the STIR/SHAKEN signature received with a call arriving on these trunks — passthrough, passthrough-block, resign, or keep-sign (default).
transmit-passport no Egress: which STIR/SHAKEN headers are emitted on calls dialed out these trunks — transmit-full (default), transmit-verstat, or strip.
uri-selection no Order the trunk’s URIs are tried: sequential (default) or random.
ring-for no Per-URI ring duration in seconds. Default 90.
codecs no Ordered codec policy (below). Empty = no restriction.
max-concurrent no Template per-trunk concurrent-call cap. A trunk’s own value overrides it; capacity is never pooled across trunks.

The mode and the two contexts split cleanly by direction:

Ingress (caller-id-mode, inbound-caller-id-context, and the trunk’s allowed-caller-ids) governs what a peer may assert when sending you a call:

Mode Behavior
fixed Discard the asserted value; use the trunk’s primary-caller-id. The allowlist is skipped — the value is yours. Every trunk on a fixed profile must set a valid primary-caller-id.
provided Accept the asserted value, subject to allowed-caller-ids.
modify Rewrite the asserted value through inbound-caller-id-context, then screen the result against allowed-caller-ids.

Setting inbound-caller-id-context with a mode other than modify is a validation error — it would be silently inert.

Egress (outbound-caller-id-context only) rewrites the caller ID a call is carrying just before it leaves — format conversion for a carrier, scrubbing, branding. Mode and allowlist do not run on egress: the caller ID on an outbound call arrived with the call (a line’s enforced number, a PSTN caller’s number on a forwarded call) and was already policed where it entered.

STIR/SHAKEN: ingress fixes the policy, egress emits it

Section titled “STIR/SHAKEN: ingress fixes the policy, egress emits it”

The signing knobs are ingress properties: they are read from the trunk a call arrives on, travel with the call, and are realized when the call leaves — the egress trunk’s own signing knobs are never consulted. This is deliberate anti-laundering: a call cannot pick up a better attestation by leaving through a differently-configured trunk, because the level and signature disposition were fixed where the call (and its trust) entered.

attestation-mode — the attestation level your namespace asserts when it signs a fresh PASSporT for a call that arrived on these trunks. Required on every profile when your stirshaken policy has outbound signing enabled; ignored entirely when the namespace has no stirshaken block.

Level Assert it when
A You know the customer and their right to the number — e.g. a trunk pinned by allowed-caller-ids to its assigned DIDs
B Known customer, unverified number
C Gateway / transit traffic

Choosing honestly is a compliance obligation, not a technical one. Line-originated calls need no profile: they are signed at A — the switch enforces line caller ID — unless the line profile sets allow-unrestricted-caller-id, which drops them to B.

resign-policy — what happens to the signature received with a call arriving on these trunks:

Value Behavior
keep-sign (default) Keep a received signature; sign the call yourself if it arrived unsigned
resign Always sign fresh, discarding whatever was received
passthrough Relay exactly what was received — including nothing; never sign
passthrough-block Relay a received signature, but reject a call that arrives unsigned

passthrough-block is the strict-interconnect stance: signed traffic passes untouched, unsigned traffic is refused at ingest.

transmit-passport — the one egress knob, controlling which STIR/SHAKEN headers actually appear on calls dialed out these trunks:

Value Emits
transmit-full (default) The Identity header (the PASSporT), plus verstat / P-Asserted-Identity
transmit-verstat verstat + P-Asserted-Identity only — no Identity header, for far ends that mishandle it
strip No STIR/SHAKEN headers at all

Unlike the signing knobs, transmit-passport needs no stirshaken block or credential — it is pure header control. The verstat / PAI conveyance additionally requires your namespace’s inbound policy to set convey-verstat.

The signature always covers the final caller ID — after outbound-caller-id-context has run. The namespace-level policy block, signing credential provisioning, and inbound verification are covered in the STIR/SHAKEN guide.

  • ring-for — how long each URI of a trunk is given before the dial moves on (next URI, next route destination, or failure). The 90-second default suits carrier egress; customer-PBX delivery profiles usually want something shorter so DID callers aren’t left ringing into a dead PBX.
  • uri-selectionsequential treats a trunk’s URI list as primary/backup; random spreads calls across gateways.
  • codecs — same semantics as line-profile codecs: inbound offers from these trunks are stripped to the set at the edge (no common codec → SIP 488 before anything rings); outbound dials offer the set in preference order. Empty = passthrough. Match this to what the peer actually supports — a mismatch surfaces as 488s in the peer’s logs.
trunk-profiles:
# Carrier origination: the whole PSTN calls in; trust presented CID and
# relay the carrier's signatures untouched — never re-vouch PSTN transit.
carrier-ingress:
inbound-translation-context: from-carrier
outbound-translation-context: to-carrier # required; used if ever dialed
caller-id-mode: provided
attestation-mode: C # if we ever sign a call from here: gateway level
resign-policy: passthrough
codecs: [ulaw, g722]
# Carrier termination: normalize egress CID to E.164; emit full S/S headers.
carrier-egress:
inbound-translation-context: from-carrier
outbound-translation-context: to-carrier
caller-id-mode: provided
outbound-caller-id-context: egress-e164
attestation-mode: C # ingress knob; required whenever the ns signs
transmit-passport: transmit-full # the default, shown for clarity
uri-selection: random
codecs: [ulaw]
# Customer PBX: normalize what they assert, screen it, vouch it fresh at A.
customer-pbx-profile:
inbound-translation-context: from-customers
outbound-translation-context: to-customers
caller-id-mode: modify
inbound-caller-id-context: normalize-cid
outbound-caller-id-context: delivery-format
attestation-mode: A # allowed-caller-ids pins them to their DIDs
resign-policy: resign # discard whatever the PBX attached; we vouch
ring-for: 30
codecs: [ulaw, g722]