# Trunk media and edge tuning

> Packetization, carrier source CIDRs, regional SIP edge pinning, the RTP port range, per-trunk auto barge-in, and the registration status chip on the Trunks screen.

The **Trunks** admin screen exposes a set of per-trunk media and edge
controls that do not appear on the trunk creation walkthrough: RTP
packetization, a carrier source-address allowlist, a regional SIP edge
pin, the RTP port window, and auto barge-in. This page explains what each
field does, what it serialises to, and how the status chip in the trunk
list is derived.

All of these fields live on the `trunk` row and are carried in the Redis
payload, so they can be changed on an existing trunk without recreating
it.

## How a trunk save reaches the gateway

Every write on this screen is **double-published**:

1. The non-secret row is written to the Postgres/Supabase `trunk` table.
   This is what the SPA lists and filters on.
2. The same trunk is mirrored into Redis (`trunks_data`) through
   `adminApi.addTrunk` / `adminApi.updateTrunk`, so the TeleQuick
   C++ gateway picks the config up immediately.

Two consequences are worth internalising before you tune anything:

- **Order matters.** Postgres first, then the Redis mirror. A trunk that
  exists only in Redis still routes calls but is invisible on this
  screen.
- **Secrets are never hydrated back into the form.** `sip_password` and
  `api_bearer_token` load as empty strings. Blank means *keep what is
  stored*; typing a value rotates it. Their only home is the sealed
  Redis payload — the `trunk` table is RLS-readable by every org member.
- Deactivating a trunk (`active = false`) is a soft delete: the row stays
  in Postgres but is pulled from Redis, so the gateway stops routing
  through it.

Numeric inputs on this form are truncated and clamped to a range before
they are sent. An out-of-range value is clamped rather than rejected, and
an empty or non-numeric entry falls back to the low bound — this replaced
an older path where a stray digit in a port field failed the entire save
or wrote silently-invalid SIP/RTP config.

## Packetization and the rtp_ptime_ms field

`rtp_ptime_ms` is the packetization interval in milliseconds — how much
audio each RTP packet carries on this trunk.

The field defaults to **unset** (`null`). Unset means the trunk does not
force a ptime and the value is left to normal codec/offer negotiation.
The payload builder coerces a falsy value to `null`, so clearing the
field (or entering `0`) is the same as "not set".

When you do set it, you are trading packet rate against delay:

| Direction | Effect |
| --------- | ------ |
| Larger ptime | Fewer packets per second, so less per-packet header and per-packet CPU overhead on both ends. Each lost packet takes proportionally more audio with it, and the extra buffering time is added to one-way latency. |
| Smaller ptime | More packets per second and more overhead, but less packetization delay and finer-grained loss. |

Set a larger ptime when you are packet-rate bound (high channel counts on
a constrained edge, or a carrier that asks for it). Keep it small — or
unset — on legs where responsiveness is the point, such as an AI agent
leg where auto barge-in has to interrupt quickly: packetization delay
sits in front of every barge-in decision the gateway makes.

A ptime you set here is still an offer. If the carrier does not accept
it, the negotiated value wins.

## Carrier source CIDRs and the digest challenge

`acl_allow_cidrs` is a list of source CIDRs for this trunk. It is
validated in the form with the shared CIDR validator, so a malformed
entry blocks the save rather than reaching the gateway.

- **Empty list.** The payload sends `null` — no source allowlist. Inbound
  requests on the trunk are authenticated the ordinary way, by digest
  challenge against `sip_username` / `sip_password`.
- **Populated list.** The listed carrier source ranges are recognised by
  address, so carrier traffic is admitted on IP identity instead of being
  challenged. Many PSTN carriers will not answer a digest challenge at
  all, which is exactly the case this field exists for.

Keep the list as tight as the carrier will let you publish. Anything in
these ranges is treated as the carrier for this trunk.

The related registration fields are separate knobs:

| Field | Default | Meaning |
| ----- | ------- | ------- |
| `require_registration` | `false` | Whether this trunk performs an outbound REGISTER. |
| `register_expires_sec` | `3600` | Registration expiry carried in the payload. |

IP-authenticated carrier trunks commonly run with
`require_registration = false`; credentialed SIP accounts set it true.

## Pinning a trunk to a regional SIP edge

`sip_edge_id` pins the trunk to a row in the platform `sip_edge`
registry. The picker is populated from the enabled edges, which are
readable by every tenant. `null` is the **direct** option — no regional
edge in the path.

When you pin an edge, two things happen that you do not configure by
hand:

- On save, the BFF **derives `proxy` and `internal_sip_ip` from the
  edge**. You should not expect to hand-maintain those two fields on a
  pinned trunk; the edge selection owns them.
- The edge's Kamailio **pulls this trunk's carrier IPs into its
  allowlist**, so the edge will pass traffic for this trunk.

Because the edge's allowlist is derived from the trunk's carrier
addressing, pinning an edge on a trunk whose carrier source ranges are
blank gives the edge nothing to allow. Fill in the carrier addressing and
`acl_allow_cidrs` before, or at the same time as, pinning the edge.

Pin an edge when the carrier requires signalling from a particular region
or a stable set of egress addresses. Leave the trunk direct when the
carrier peers with your gateway addresses already.

## RTP port range and concurrency

Two fields define the media port window the gateway uses for this trunk:

| Field | Default |
| ----- | ------- |
| `rtp_start_port` | `16384` |
| `rtp_end_port` | `32768` |

Both are carried in the Redis payload, so the gateway sees a change as
soon as the save completes. Whatever window you choose has to be open
end-to-end: through your firewall and through any NAT, toward the trunk's
`external_rtp_ip`.

`channel_limit` (default `50`) is a separate number. It caps the
concurrent channels on the trunk; the port window does not enforce it and
the two are not validated against each other. If you raise
`channel_limit`, check that the port window is still wide enough for the
media sessions that limit now permits, and that the firewall rule matches
the window rather than the old one.

Both values are clamped by the form before they are sent, so a mistyped
port lands at the boundary instead of propagating a nonsense value into
Redis.

## Auto barge-in mode and aggressiveness

Barge-in is configured **per trunk**, not only globally, so a carrier
trunk and an AI-vendor trunk on the same org can behave differently.

| Field | Default | Notes |
| ----- | ------- | ----- |
| `auto_bargein_mode` | `energy` | The detection mode used on this trunk. |
| `auto_bargein_aggressiveness` | `2` | Integer, clamped by the form. Higher values make the detector interrupt more readily. |

Energy-based detection keys off incoming audio level, which makes it
sensitive to the acoustic conditions of the leg. A noisy PSTN leg with an
aggressive setting will cut prompts off on background noise; a quiet,
well-conditioned agent leg tolerates more aggression and feels more
responsive. Tune the value on the trunk the complaints are coming from,
rather than changing it for every trunk at once.

Both fields ride the same payload as everything else here, so the gateway
applies the new behaviour to calls placed after the save.

## Reading the registration status chip

The chip in the trunk list has **two possible sources**, and knowing
which one you are looking at matters.

**Live registration state.** On load the screen calls
`adminApi.trunkRegState(orgId)`, which returns a per-trunk record:

| Field | Meaning |
| ----- | ------- |
| `state` | The engine-written outbound REGISTER state. |
| `code` | The SIP response code recorded with that state. |
| `ts_ms` | When the state was recorded, in epoch milliseconds. |

This call is **best effort**. It runs in parallel with the table load and
its rejection is swallowed — the list still renders if the lookup fails.

**The stored `status` column.** When live state is unavailable, the chip
falls back to the `status` column on the `trunk` row, which is one of:

| Value | |
| ----- | --- |
| `connected` | Also the value any unrecognised stored value narrows to. |
| `disconnected` | |
| `error` | |

`status` is a **save-time constant**, not a health signal. It is written
with the row; it does not update as the trunk's registration comes and
goes.

Practical reading of the chip:

- A chip backed by live state, with a recent `ts_ms`, is a real
  registration signal. Use `code` to tell an auth failure from a
  reachability failure.
- A chip backed only by `status` tells you what was stored at save time
  and nothing about right now. A green chip here is not proof that the
  trunk is registered.
- A trunk with `require_registration = false` never produces outbound
  REGISTER state at all, so it will always read from the stored `status`.

The engine performs its REGISTER on save, so after editing a
credentialed trunk, reload the screen to pick up the resulting live
state rather than trusting the chip you were already looking at.

## Field reference

Every field below is persisted on the `trunk` row and mirrored into the
Redis payload the gateway reads.

| Field | Default | Serialised as |
| ----- | ------- | ------------- |
| `rtp_ptime_ms` | `null` (unset) | `null` when blank or zero |
| `acl_allow_cidrs` | `[]` | `null` when the list is empty |
| `sip_edge_id` | `null` (direct) | `null` when direct |
| `rtp_start_port` | `16384` | integer, clamped |
| `rtp_end_port` | `32768` | integer, clamped |
| `channel_limit` | `50` | integer, clamped |
| `require_registration` | `false` | boolean |
| `register_expires_sec` | `3600` | integer |
| `auto_bargein_mode` | `energy` | string |
| `auto_bargein_aggressiveness` | `2` | integer, clamped |
| `media_mode` | `proxy` | `direct` or `proxy` |
| `codec_preferences` | `["PCMA","PCMU"]` | array, in preference order |

The endpoint banner above the table shows your org's SIP and WebRTC
domains and the signalling ports TeleQuick listens on:
`5060 UDP/TCP` and `5061 TLS`.

## Related

- [Telemetry](/platform/telemetry) — the RTP, jitter, and MOS metrics to
  watch after changing ptime or the port window
- [Telephony Metrics](/glossary/metrics) — healthy ranges for the audio
  quality numbers these settings move
