@php $endpoint = $endpoint ?? null; $auth = $auth ?? null; $aor = $aor ?? null; $reg = $reg ?? null; $trunk = $trunk ?? null; $isEdit = $isEdit ?? false; $codecs = $codecs ?? []; if (empty($codecs)) { $codecs = [ 'ulaw' => ['label' => 'G.711 ulaw', 'hz' => '8 kHz', 'preferred' => true], 'alaw' => ['label' => 'G.711 alaw', 'hz' => '8 kHz', 'preferred' => true], 'g729' => ['label' => 'G.729', 'hz' => '8 kHz', 'preferred' => false], 'g722' => ['label' => 'G.722', 'hz' => '16 kHz', 'preferred' => false], 'opus' => ['label' => 'Opus', 'hz' => '48 kHz', 'preferred' => false], 'gsm' => ['label' => 'GSM', 'hz' => '8 kHz', 'preferred' => false], ]; } $v = function ($key, $default = '') use ($endpoint, $auth, $aor, $reg, $trunk) { $map = [ // Endpoint fields 'id' => optional($endpoint)->id, 'description' => optional($endpoint)->description, 'transport' => optional($endpoint)->transport ?: 'transport-udp', 'context' => optional($endpoint)->context ?: 'from-trunk', 'from_user' => optional($endpoint)->from_user ?: optional($trunk)->from_user, 'from_domain' => optional($endpoint)->from_domain ?? optional($trunk)->from_domain, 'outbound_proxy' => optional($endpoint)->outbound_proxy ?? optional($trunk)->outbound_proxy, 'rtp_timeout' => optional($endpoint)->rtp_timeout, 'rtp_timeout_hold' => optional($endpoint)->rtp_timeout_hold, 'identify_by' => optional($endpoint)->identify_by ?: 'username', 'dtmf_mode' => optional($endpoint)->dtmf_mode ?: 'rfc4733', 'direct_media' => (optional($endpoint)->direct_media ?? 'no') === 'yes', 'preferred_codec_only' => (optional($endpoint)->preferred_codec_only ?? 'no') === 'yes', 'rtcp_mux' => (optional($endpoint)->rtcp_mux ?? 'no') === 'yes', 'trust_id_inbound' => (optional($endpoint)->trust_id_inbound ?? 'no') === 'yes', 'trust_id_outbound' => (optional($endpoint)->trust_id_outbound ?? 'no') === 'yes', 'send_pai' => (optional($endpoint)->send_pai ?? 'no') === 'yes', 'send_rpid' => (optional($endpoint)->send_rpid ?? 'no') === 'yes', 'user_eq_phone' => (optional($endpoint)->user_eq_phone ?? 'no') === 'yes', // NAT-safe defaults for a NEW trunk (no $endpoint yet): symmetric RTP // and force_rport prevent one-way / no audio behind NAT, which is the // norm for our on-prem boxes. On edit, the saved value is honoured. 'rtp_symmetric' => (optional($endpoint)->rtp_symmetric ?? ($endpoint ? 'no' : 'yes')) === 'yes', 'rewrite_contact' => (optional($endpoint)->rewrite_contact ?? 'no') === 'yes', 'force_rport' => (optional($endpoint)->force_rport ?? ($endpoint ? 'no' : 'yes')) === 'yes', 'force_outbound_cli' => optional($endpoint)->force_outbound_cli, 'callerid' => optional($endpoint)->callerid ?? optional($trunk)->caller_id, 'per_extension_cli' => (bool) (optional($trunk)->per_extension_cli ?? 0), 'outbound_auth' => optional($endpoint)->outbound_auth, 'auth' => optional($endpoint)->auth, 'inbound_auth' => optional($endpoint)->auth, 'language' => optional($endpoint)->language, 'accountcode' => optional($endpoint)->accountcode, 't38_udptl' => (optional($endpoint)->t38_udptl ?? 'no') === 'yes', 't38_udptl_ec' => optional($endpoint)->t38_udptl_ec, 't38_udptl_nat' => (optional($endpoint)->t38_udptl_nat ?? 'no') === 'yes', 't38_udptl_ipv6' => (optional($endpoint)->t38_udptl_ipv6 ?? 'no') === 'yes', 'timers' => (optional($endpoint)->timers ?? 'no') === 'yes', 'timers_min_se' => optional($endpoint)->timers_min_se ?: 90, 'timers_sess_expires' => optional($endpoint)->timers_sess_expires ?: 1800, '100rel' => (optional($endpoint)->{'100rel'} ?? 'no') === 'yes', 'connected_line_method' => optional($endpoint)->connected_line_method ?: 'invite', 'media_encryption' => optional($endpoint)->media_encryption ?: 'no', 'use_avpf' => (optional($endpoint)->use_avpf ?? 'no') === 'yes', 'use_ptime' => (optional($endpoint)->use_ptime ?? 'no') === 'yes', 'ice_support' => (optional($endpoint)->ice_support ?? 'no') === 'yes', 'acl' => optional($endpoint)->acl, 'allow' => optional($endpoint)->allow, 'callerid_privacy' => optional($endpoint)->callerid_privacy, 'callerid_tag' => optional($endpoint)->callerid_tag, 'call_group' => optional($endpoint)->call_group, 'pickup_group' => optional($endpoint)->pickup_group, 'named_call_group' => optional($endpoint)->named_call_group, 'named_pickup_group' => optional($endpoint)->named_pickup_group, 'device_state_busy_at' => optional($endpoint)->device_state_busy_at, 'disable_direct_media_on_nat' => (optional($endpoint)->disable_direct_media_on_nat ?? 'no') === 'yes', 'direct_media_glare_mitigation' => optional($endpoint)->direct_media_glare_mitigation ?: 'none', 'direct_media_method' => optional($endpoint)->direct_media_method ?: 'invite', 'follow_early_media_fork' => (optional($endpoint)->follow_early_media_fork ?? 'no') === 'yes', 'ignore_183_without_sdp' => (optional($endpoint)->ignore_183_without_sdp ?? 'no') === 'yes', 'inband_progress' => (optional($endpoint)->inband_progress ?? 'no') === 'yes', 'incoming_call_offer_pref' => optional($endpoint)->incoming_call_offer_pref ?: 'local', 'outgoing_call_offer_pref' => optional($endpoint)->outgoing_call_offer_pref ?: 'remote_merge', 'overlap_context' => optional($endpoint)->overlap_context, 'redirect_method' => optional($endpoint)->redirect_method ?: 'user', 'refer_blind_progress' => (optional($endpoint)->refer_blind_progress ?? 'no') === 'yes', 'send_diversion' => (optional($endpoint)->send_diversion ?? 'no') === 'yes', 'send_history_info' => (optional($endpoint)->send_history_info ?? 'no') === 'yes', 'send_connected_line' => (optional($endpoint)->send_connected_line ?? 'no') === 'yes', 'trust_connected_line' => (optional($endpoint)->trust_connected_line ?? 'no') === 'yes', 'rpid_immediate' => (optional($endpoint)->rpid_immediate ?? 'no') === 'yes', 'tone_zone' => optional($endpoint)->tone_zone, 'tos_audio' => optional($endpoint)->tos_audio, 'tos_video' => optional($endpoint)->tos_video, 'cos_audio' => optional($endpoint)->cos_audio, 'cos_video' => optional($endpoint)->cos_video, 'sub_min_expiry' => optional($endpoint)->sub_min_expiry, 'moh_suggest' => optional($endpoint)->moh_suggest, 'voicemail_extension' => optional($endpoint)->voicemail_extension, 'mailboxes' => optional($endpoint)->mailboxes, 'max_audio_streams' => optional($endpoint)->max_audio_streams ?: 10, 'max_video_streams' => optional($endpoint)->max_video_streams ?: 1, 'bundle' => (optional($endpoint)->bundle ?? 'no') === 'yes', 'webrtc' => (optional($endpoint)->webrtc ?? 'no') === 'yes', 'stir_shaken' => (optional($endpoint)->stir_shaken ?? 'no') === 'yes', 'stir_shaken_profile' => optional($endpoint)->stir_shaken_profile, 'dtls_auto_generate_cert' => (optional($endpoint)->dtls_auto_generate_cert ?? 'no') === 'yes', 'dtls_setup' => optional($endpoint)->dtls_setup ?: 'active', 'dtls_verify' => (optional($endpoint)->dtls_verify ?? 'no') === 'yes', 'dtls_rekey' => optional($endpoint)->dtls_rekey, 'dtls_fingerprint' => optional($endpoint)->dtls_fingerprint ?: 'SHA-256', 'dtls_cert_file' => optional($endpoint)->dtls_cert_file, 'dtls_private_key' => optional($endpoint)->dtls_private_key, 'dtls_ca_file' => optional($endpoint)->dtls_ca_file, 'dtls_ca_path' => optional($endpoint)->dtls_ca_path, 'dtls_cipher' => optional($endpoint)->dtls_cipher, 'srtp_tag_32' => (optional($endpoint)->srtp_tag_32 ?? 'no') === 'yes', 'media_address' => optional($endpoint)->media_address, 'media_use_received_transport' => (optional($endpoint)->media_use_received_transport ?? 'no') === 'yes', 'rtp_keepalive' => optional($endpoint)->rtp_keepalive, 'rtp_ipv6' => (optional($endpoint)->rtp_ipv6 ?? 'no') === 'yes', 'rtcp_mux' => (optional($endpoint)->rtcp_mux ?? 'no') === 'yes', 'rtcp_mux' => (optional($endpoint)->rtcp_mux ?? 'no') === 'yes', 'record_on_feature' => optional($endpoint)->record_on_feature, 'record_off_feature' => optional($endpoint)->record_off_feature, 'fax_detect' => (optional($endpoint)->fax_detect ?? 'no') === 'yes', 'fax_detect_timeout' => optional($endpoint)->fax_detect_timeout, 'g726_non_standard' => (optional($endpoint)->g726_non_standard ?? 'no') === 'yes', 'message_context' => optional($endpoint)->message_context, 'aggregate_mwi' => (optional($endpoint)->aggregate_mwi ?? 'no') === 'yes', 'mwi_from_user' => optional($endpoint)->mwi_from_user, 'allow_subscribe' => (optional($endpoint)->allow_subscribe ?? 'no') === 'yes', 'allow_transfer' => (optional($endpoint)->allow_transfer ?? 'no') === 'yes', 'allow_unauthenticated_options' => (optional($endpoint)->allow_unauthenticated_options ?? 'no') === 'yes', 'allow_overlap' => (optional($endpoint)->allow_overlap ?? 'no') === 'yes', 'supress_moh_on_sendonly' => (optional($endpoint)->supress_moh_on_sendonly ?? 'no') === 'yes', 'suppress_q850_reason_headers' => (optional($endpoint)->suppress_q850_reason_headers ?? 'no') === 'yes', 'subscribe_context' => optional($endpoint)->subscribe_context, 'one_touch_recording' => (optional($endpoint)->one_touch_recording ?? 'no') === 'yes', 'sdp_owner' => optional($endpoint)->sdp_owner, 'sdp_session' => optional($endpoint)->sdp_session, 'tos_audio' => optional($endpoint)->tos_audio, 'tos_video' => optional($endpoint)->tos_video, 'cos_audio' => optional($endpoint)->cos_audio, 'cos_video' => optional($endpoint)->cos_video, 'allow' => optional($endpoint)->allow, // Auth fields 'username' => optional($auth)->username ?? optional($trunk)->username, 'password' => optional($auth)->password ?? optional($trunk)->password, 'auth_type' => optional($auth)->auth_type ?? optional($trunk)->auth_type, 'realm' => optional($auth)->realm, // AOR fields 'contact' => optional($aor)->contact, 'max_contacts' => optional($aor)->max_contacts ?? optional($trunk)->max_contacts, 'qualify_frequency' => optional($aor)->qualify_frequency, 'qualify_timeout' => optional($aor)->qualify_timeout, 'remove_existing' => (optional($aor)->remove_existing ?? 'no') === 'yes', 'support_path' => (optional($aor)->support_path ?? 'no') === 'yes', // Registration fields 'reg_server_uri' => optional($reg)->server_uri ?? optional($trunk)->server_uri, 'reg_client_uri' => optional($reg)->client_uri ?? optional($trunk)->client_uri, 'reg_contact_user' => optional($reg)->contact_user ?? optional($trunk)->contact_user, 'retry_interval' => optional($reg)->retry_interval, 'expiration' => optional($reg)->expiration, 'max_retries' => optional($reg)->max_retries, 'forbidden_retry_interval' => optional($reg)->forbidden_retry_interval, 'fatal_retry_interval' => optional($reg)->fatal_retry_interval, 'auth_rejection_permanent' => (optional($reg)->auth_rejection_permanent ?? 'no') === 'yes', 'line' => optional($reg)->line, 'reg_endpoint' => optional($reg)->endpoint, // Trunk (sip_trunks table) fields 'dial_prefix' => optional($trunk)->dial_prefix, 'dial_strip' => optional($trunk)->dial_strip, 'dial_prepend' => optional($trunk)->dial_prepend, 'callerid_name' => optional($trunk)->trunk_cid_name, 'enabled' => !optional($trunk)->disable_trunk, 'notes' => optional($trunk)->notes, ]; return $map[$key] ?? $default; }; @endphp @php $selectedCodecs = old('codecs'); if ($selectedCodecs === null) { $raw = $endpoint->allow ?? ''; $selectedCodecs = $raw ? preg_split('/\s*,\s*/', $raw, -1, PREG_SPLIT_NO_EMPTY) : ['ulaw', 'alaw']; } $selectedCodecs = array_map(fn($c) => strtolower(trim($c)), (array) $selectedCodecs); @endphp
{{-- ════ LEFT SIDEBAR NAV ════ --}} {{-- ════ MAIN CONTENT ════ --}}
{{-- ══ OVERVIEW ══ --}}
{{-- Left: General Information --}}
General Information
@if($isEdit)
@else
Unique identifier used in Dial(PJSIP/...@ID). Must match what your dialplan expects.
@endif
The number presented on outbound calls. When per-extension Caller ID is on, this is the fallback used for extensions with no own CLI.
Configure in Connectivity tab
Configure in Caller ID tab
{{-- Carrier CLI Section --}}
Carrier CLI Analysis
@php $authUser = $v('username') ?? ''; $fromUser = $v('from_user') ?? ''; $forceCli = $v('force_outbound_cli') ?? ''; $hasMismatch = ($authUser && $fromUser && $authUser !== $fromUser) || ($authUser && $forceCli && $authUser !== $forceCli); @endphp @if($hasMismatch)
⚠ Warning: Caller ID differs from authentication username. Carrier may reject outbound calls.
@else
✓ OK: Caller ID matches authentication username.
@endif
{{-- Enable Trunk toggle --}}
Enable Trunk
Trunk is enabled and available for use
{{-- Right: Allowed Codecs --}}
Allowed Codecs
@if(!empty($codecs)) @foreach($codecs as $key => $meta) @php $keyL = strtolower($key); $label = is_array($meta) ? ($meta['label'] ?? $key) : $meta; $hz = is_array($meta) ? ($meta['hz'] ?? '') : ''; $pref = is_array($meta) ? ($meta['preferred'] ?? false) : false; @endphp
@if($pref) Preferred @endif
{{ $hz }}
@endforeach @endif
{{-- ══ CONNECTIVITY ══ --}}
Transport & Proxy How SIP signalling travels to this provider
@php $storedTransport2 = strtolower($v('transport')); if (!str_starts_with($storedTransport2, 'transport-')) { $storedTransport2 = 'transport-' . $storedTransport2; } $availableTransports2 = $transports ?? ['transport-udp', 'transport-tcp', 'transport-tls']; @endphp
Current: {{ $v('transport') ?: 'transport-udp' }}
Leave blank if no outbound proxy is required
Contact & Address of Record (AOR) Where to send and receive SIP traffic
Used for static contacts (no registration). Leave blank if registering.
Remove Existing ?
Support Path ?
Digit Manipulation Transform dialed numbers for carrier requirements
Example: 27768024199 → Strip 2, Prepend 0 → 0768024199
{{-- ══ CALLER ID & IDENTITY ══ --}}
Caller ID & Identity
{{-- Per-extension caller ID toggle --}}
Use per-extension caller ID
When on, each extension presents its own External Caller ID on outbound calls, falling back to the Caller ID Number below when an extension has none. When off, every call uses this trunk's single caller ID.
Configured in Overview
Default caller ID number
Default caller ID name
Override caller ID for all calls through this trunk

Trust ID Outbound
Trust caller ID from trusted sources
Trust ID Inbound
Trust caller ID from inbound calls
Send PAI
Send P-Asserted-Identity header
Send RPID
Send Remote-Party-ID header
User = Phone
Treat user as phone number
{{-- ══ SIGNALLING ══ --}}
Signalling
Configure in Security tab
Configure in Connectivity tab
@foreach([ ['direct_media', 'Direct Media', 'Allow media to flow directly between endpoints bypassing Asterisk'], ['preferred_codec_only','Preferred Codec Only','Offer only the first/preferred codec in SDP'], ['rtcp_mux', 'RTCP Mux', 'Multiplex RTP and RTCP on the same port (RFC 5761)'], ['rtp_symmetric', 'RTP Symmetric', 'Send RTP to the address/port from which it was received'], ['rewrite_contact', 'Rewrite Contact', 'Rewrite the Contact header with the address used to reach Asterisk'], ['force_rport', 'Force RPort', 'Force symmetric response port (RFC 3581)'], ] as [$fname, $flabel, $ftip])
{{ $flabel }} ?
@endforeach
{{-- ══ MEDIA ══ --}}
Media

Seconds between RTP keepalive packets (0 = disabled)
Configure in Security tab
ICE Support
Use AVPF
Use PTime
{{-- ══ AUTHENTICATION ══ --}}
Authentication
@if($isEdit)
Leave blank to keep the existing password.
@endif
Automatically set when username/password auth is configured
Automatically set when username/password auth is configured
{{-- ══ REGISTRATION ══ --}}
Provider Registration
Leave blank to auto-use the Authentication Username — keeps the account consistent.
Automatically set when username/password auth is configured
Permanent Auth Rejection
{{-- CLI Mapping Card --}}
CLI Mapping
Map extension numbers to carrier-approved CLIs for carriers that reject extension numbers
@php $cliMappings = DB::table('trunk_cli_mapping')->where('trunk_id', optional($endpoint)->id ?? '')->get(); @endphp @if($cliMappings->isEmpty())
No CLI mappings configured
@else @foreach($cliMappings as $mapping)
@endforeach @endif
{{-- ══ TEAMS ══ --}}
Microsoft Teams Preset Values
DescriptionMSTeams
Transporttransport-tls
Codecsulaw, alaw, gsm
DTMFrfc4733
Usernamemsteams
Contactsip:sip.pstnhub.microsoft.com
From Domainmsteams.nextfour.co.za
Identify Byauth_username
Apply Teams Defaults

Fills the form with a Microsoft Teams Direct Routing baseline. You can adjust values after applying.

{{-- ══ ADVANCED ══ --}}
Dialplan & Context
Locked to trunks-in so inbound DID routes are always reached.
Session Timers SIP session refresh / keep-alive settings
SIP Reliability & Early Media 100rel, progress tone and media fork behaviour
100rel (PRACK) ?
Reliable 183/180 responses
Inband Progress ?
183 with inband ringback
Follow Early Media Fork ?
Ignore 183 Without SDP ?
{{-- ══ SECURITY ══ --}}
Access Control & ACL
Leave blank for no IP restriction
Media Encryption
SRTP Tag 32 ?
Routing

Outbound route associations are configured in Outbound Routes. Assign this trunk there to control which calls are sent through it.

{{-- end etf-content --}} {{-- ════ STATUS PANEL ════ --}}
Trunk Status
@if($isEdit)
Checking...
@else
Not Yet Created
@endif
@if($isEdit)
Registration —
Last Reg —
OPTIONS —
Active Calls —
@else
Endpoint N/A
Auth N/A
AOR N/A
Registration N/A
@endif
@if($isEdit)
Host Validation
Enter a host above to validate
@endif
{{-- end etf-shell --}} {{-- ════ STICKY ACTION BAR ════ --}}
Cancel