Signals Docs
SIP Trunks

SIP Trunks quickstart

In this quickstart you create an outbound policy, create a trunk for an office PBX that uses it, allow a second PBX address, run a configuration test and place a test call from your PBX. Every step uses curl against https://api.signals.pressone.africa/v1.

The example PBX has the public addresses 203.0.113.10 and 203.0.113.11 and accepts SIP over TLS on port 5061. Replace them with your own.

Before you start

You need:

  • An API key with the admin or engineer role. It looks like sk_live_.... See Authentication.
  • The ID of the Signals project the trunk belongs to. GET /v1/projects lists your projects.
  • The public IP address of your PBX, and the address and port where it accepts SIP.

Set them as shell variables so you can paste the commands below:

TerminalCode
export SIGNALS_API_KEY="sk_live_..." export SIGNALS="https://api.signals.pressone.africa/v1" export PROJECT_ID="prj_4f1c2a9e-7b3d-4e8a-9c61-2d5b8e0f3a17"

Three headers appear throughout:

  • Idempotency-Key makes a create or action safe to retry. Send a new unique value for each new request, and the same value when you retry that request.
  • If-Match carries the ETag of the trunk or policy you are changing, a quoted integer such as "1". It stops you from overwriting a change someone else made. Every successful edit returns a new ETag.
  • X-Project-ID selects a project. You can leave it out while your organization has one active project.

Every change is live once the API accepts it. There is nothing to publish. Changes take effect within a minute.

Responses below are trimmed to the fields this guide uses.

Step 1: Read the platform endpoints

Fetch the addresses your PBX and firewall need:

TerminalCode
curl "$SIGNALS/platform/endpoints" \ -H "Authorization: Bearer $SIGNALS_API_KEY"
JSONCode
{ "signaling_fqdn": "sip.trunk.pressone.co", "signaling_nodes": ["198.51.100.10"], "signaling_ports": [ { "transport": "udp", "port": 5060 }, { "transport": "tcp", "port": 5060 }, { "transport": "tls", "port": 5061 } ], "tls": { "realm": "sip.trunk.pressone.co", "trust_chain": [] }, "media_cidrs": ["198.51.100.10/32"], "rtp_ports": { "start": 10000, "end": 20000 } }

You will use these values in Connect your PBX.

Step 2: Create an outbound policy

Outbound calls need an outbound policy. Create one with a daily spend cap of ₦50,000 (5,000,000 kobo) and recording off:

TerminalCode
curl -X POST "$SIGNALS/outbound-policies" \ -H "Authorization: Bearer $SIGNALS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 9e3a1c76-0b4d-4a28-8f97-4d2c6e1b8a05" \ -H "X-Project-ID: $PROJECT_ID" \ -d '{ "name": "office-calls", "concurrency": null, "cps": null, "max_call_duration_seconds": 3600, "max_destination_rate_minor": null, "daily_spend_limit_minor": 5000000, "recording_mode": "off", "recording_channels": 2, "recording_retention_days": 30 }'

The API returns 201 Created with an ETag of "1". The policy is live. A new policy allows calls to Nigeria:

JSONCode
{ "id": "pol_01992f3b-0a27-7f13-8c4e-3b9d6a1e5f20", "name": "office-calls", "status": "active", "currency": "NGN", "revision": 1, "attached_services": 0, "destinations": { "allowed_countries": ["NG"], "allowed_prefixes": 0, "denied_prefixes": 0 } }
TerminalCode
export POLICY_ID="pol_01992f3b-0a27-7f13-8c4e-3b9d6a1e5f20"

Step 3: Create the trunk

Create a trunk that carries calls in both directions, accepts calls from 203.0.113.10, requires TLS (require_tls), delivers inbound calls to the PBX over TLS on port 5061 and uses the policy from step 2:

TerminalCode
curl -X POST "$SIGNALS/trunks" \ -H "Authorization: Bearer $SIGNALS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 6c0f9a52-3e1b-4d7a-8f25-1b9e7d4c2a60" \ -H "X-Project-ID: $PROJECT_ID" \ -d '{ "name": "office-pbx", "subaccount_id": null, "direction": "both", "auth_mode": "ip_allow_list", "sources": ["203.0.113.10/32"], "require_tls": true, "source_validation": "strict", "targets": [ { "sip_uri": "sip:203.0.113.10:5061", "priority": 10, "weight": 100, "transport": "tls" } ], "codecs": ["PCMA", "PCMU"], "media_security": "rtp", "limits": { "concurrency": 30, "cps": 5, "max_call_duration_seconds": 7200 }, "outbound_policy_id": "'"$POLICY_ID"'", "number_block_ids": [] }'

number_block_ids routes whole number blocks to the trunk in the same request. This example leaves it empty and routes numbers later in Numbers and routing.

The API returns 201 Created with the trunk under trunk and an ETag header of "1":

JSONCode
{ "trunk": { "id": "trk_01992f3a-6c1e-7d42-9b8a-5e7f0c2d1a94", "project_id": "prj_4f1c2a9e-7b3d-4e8a-9c61-2d5b8e0f3a17", "name": "office-pbx", "status": "enabled", "direction": "both", "revision": 1, "signaling_fqdn": "office-pbx.sip.trunk.pressone.co", "currency": "NGN", "authentication": { "mode": "ip_allow_list", "sources": [ { "id": "01992f3a-6c20-7a11-8d3e-2f4b6a9c0e15", "cidr": "203.0.113.10/32", "enabled": true } ], "digest": null, "require_tls": true, "source_validation": "strict" }, "routing": [ { "id": "01992f3a-6c21-7b02-9e4f-3a5c7b0d1f26", "sip_uri": "sip:203.0.113.10:5061", "priority": 10, "weight": 100, "transport": "tls", "enabled": true } ], "media": { "codecs": ["PCMA", "PCMU"], "security": "rtp" }, "policy": { "id": "pol_01992f3b-0a27-7f13-8c4e-3b9d6a1e5f20", "name": "office-calls" } }, "digest_password": null }

The trunk is live and takes effect on the network within a minute. Save the ID:

TerminalCode
export TRUNK_ID="trk_01992f3a-6c1e-7d42-9b8a-5e7f0c2d1a94"

The trunk name must be lowercase letters, digits and hyphens, up to 63 characters, starting and ending with a letter or digit. Signals uses it in the trunk's signaling_fqdn.

If your PBX cannot use a fixed public IP address, create the trunk with "auth_mode": "digest", "sources": [] and a digest_realm instead. The response then carries the generated password in digest_password. It is shown only once, so store it before you do anything else. See Authentication and security.

If the trunk could not run as configured, the API refuses the create with 422 and a problem detail that starts with the trunk would not apply:. Nothing is created.

Step 4: Allow a second PBX address

Your office has a second PBX at 203.0.113.11. Add it as an IP credential. Send the ETag from step 3 in If-Match:

TerminalCode
curl -X POST "$SIGNALS/trunks/$TRUNK_ID/ip-credentials" \ -H "Authorization: Bearer $SIGNALS_API_KEY" \ -H "Content-Type: application/json" \ -H 'If-Match: "1"' \ -H "Idempotency-Key: 2b7d4e18-9a3c-4f05-b6e1-7c8a0d2f5e93" \ -d '{ "cidr": "203.0.113.11/32" }'
JSONCode
{ "id": "01992f3b-1a40-7e62-a0c1-5d7e9f2b4a37", "cidr": "203.0.113.11/32", "selector_precedence": 100, "enabled": true, "created_at": "2026-10-01T09:16:04Z" }

The response carries the new ETag "2". The new address is allowed within a minute.

Step 5: Run a configuration test

Ask Signals to check the trunk's configuration:

TerminalCode
curl -X POST "$SIGNALS/trunks/$TRUNK_ID/tests" \ -H "Authorization: Bearer $SIGNALS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 8a1d5c3e-2f74-4b90-a6e8-3c7b1d9f0e26" \ -d '{ "kind": "config_validation" }'

The API returns 202 Accepted and a Location header that ends with the test ID. Read the test:

TerminalCode
curl "$SIGNALS/trunks/$TRUNK_ID/tests/tst_01992f3d-0b62-7c48-9d15-6e2a4f8c1b93" \ -H "Authorization: Bearer $SIGNALS_API_KEY"
JSONCode
{ "run": { "id": "tst_01992f3d-0b62-7c48-9d15-6e2a4f8c1b93", "result": "passed", "checks": [ { "sequence": 1, "id": "configuration", "state": "passed", "summary": "2 IP and 0 digest identities, 1 termination targets, admission admit." } ], "finding": null }, "requested_kind": "config_validation", "next_sequence": 1 }

While result is running, read the test again. GET /v1/trunks/{trunk_id}/tests lists earlier runs under data.

Step 6: Place a test call from your PBX

Point your PBX at sip.trunk.pressone.co as described in Connect your PBX, then dial a Nigerian mobile number from an extension, for example +2348035550142.

  • If the call connects and you hear audio both ways, the trunk works.
  • If the call is refused straight away, check that the PBX sends from an allowed address and over TLS, since this trunk requires it.
  • If the call connects with no audio, allow the Signals media addresses and RTP ports from step 1 in your firewall.

The call needs a caller ID that is routed to the trunk. Until you route a number block in Numbers and routing, outbound calls have no caller ID they may present.

What you built

  • An outbound policy that allows calls to Nigeria and caps daily spend at ₦50,000.
  • A trunk, office-pbx, that uses that policy, accepts calls from two office addresses and delivers inbound calls over TLS.

Next steps

Last modified on