Skip to main content

Trunks

Carrier connections. A trunk is yours (shared with tenants) or a tenant’s own. A tenant’s own trunk is never selectable by another tenant. That is enforced in carrier selection, not merely hidden in the interface — otherwise it would be unmetered calling on somebody else’s carrier account, on their bill.
No trunk credential is ever returned by the API. They are stored encrypted and the panel has no way to display one.

A trunk’s own page

Click a trunk’s name. It answers, in the order the questions get asked:
  • Health — whether it is registered, whether its configuration reached the switch, and whether anything is authorised to call in on it.
  • Channels now — how many are up, against the total and outbound caps.
  • Who may call in — the address list, editable there.
  • Usage — what it carried, spend by organisation, and why calls ended.
  • Its calls, an unanswered-only view, and recent warnings that mention it. Carrier said is the carrier’s own answer to each call — 403, 503, 486 — which Ended (this side’s cause) does not carry. A red one is a refusal somebody has to fix.

Offers to organisations

Organisation admins choose their own outgoing routes, by destination, from offers you make on Outbound routing → Offers to organisations. An offer is a name organisations see (“Standard”, “Premium”) for one trunk at one service level — Standard = Voxbeam · Gold — offered to every organisation or just one. Organisations see the name and your description, never the carrier, its prefixes or its dial strings; their choices become outbound rules for their own organisation only, and they cannot name a trunk directly. An offer an organisation is using cannot be withdrawn until those rules are removed or changed.

Connect, disconnect, verify

Three buttons at the top of a trunk’s page:
  • Verify runs every check that says whether this carrier can carry a call right now, and names the one that fails: in service, configuration on the switch, the carrier’s host resolving, the gateway as the switch reports it (registered — or loaded to answer the carrier’s challenge), a SIP OPTIONS probe from the server, inbound addresses, and the last 24 hours of calls. No answer to OPTIONS is a warning, not a failure: many carriers ignore it.
  • Disconnect takes the carrier out of service at once: no new call uses it, its gateway comes off the switch and its addresses stop being accepted, so its inbound calls are refused too. Calls already up stay up.
  • Connect puts it back. On a registering trunk that is already in service, Register now asks the switch to register immediately instead of at its next retry — what you want right after the carrier says “fixed, try again”.

A carrier that challenges calls

Some IP-authenticated carriers whitelist your address and challenge each call for a username and password (Voxbeam’s setup guide shows a gateway with register=false). Put the credentials under Carrier challenges outbound calls on the trunk: the switch then carries a non-registering gateway for it and answers the challenge. The caller’s number stays in the From header, as it was.

Who may call in

Inbound calls are accepted from listed addresses and refused from everything else. The generated list is default-deny, so a trunk with no ACL hosts accepts nothing at all — while looking enabled and healthy everywhere else.
This is the commonest real fault on a trunk. If a carrier’s calls never arrive, check its addresses first: ask the carrier for every signalling address, not just the one you registered to. Many send from a media gateway that is not the host in your configuration.
Carriers may send to port 5080 or 5060; both work, and a listed address is not challenged on either. A trunk that belongs to one organisation delivers only that organisation’s numbers; a shared trunk delivers numbers of any organisation you run. A call to a number owned elsewhere is refused, and the log says “the number belongs to an organisation this trunk does not serve” — not “unallocated”, because the number exists.

Channel limits

Channel limit counts calls in both directions — it is the number your carrier contracted. Outbound channel limit caps the outgoing share of it. Without one, a dialling burst can fill the carrier and leave nothing for customers ringing in, with every screen looking healthy. Twenty against a total of thirty keeps ten channels a burst cannot take. Leave it blank and the total is the only limit.
A call refused by either is congestion, and the log names which cap was reached — “the carrier is full” and “your own outbound reservation is full” need different fixes.

Codecs

A trunk has two codec lists, and the second one is optional.
  • Codecs for outgoing calls is what FireTone offers the carrier when it dials out — and, unless you set the second list, what a call arriving on this trunk is held to as well.
  • Codecs for incoming calls is what this carrier may offer on calls it sends you. Leave every codec unticked to accept the same list as above.
Set the second list when the carrier sends something different from what it accepts. A carrier perfectly happy to take G.729 from you may well send you G.711, and one list cannot say both.
Until this second list existed, the outgoing list governed both directions — so a trunk set to G729 to dial out cheaply was also refusing every call that carrier sent. There is no ringing when that happens: the call is refused while the codecs are still being agreed, so the caller hears a disconnection and the hangup cause is INCOMPATIBLE_DESTINATION. If you see that, compare the two lists — the daemon now logs both of them side by side, and the call’s CDR records what the far end offered under They offered.
Both lists are chosen from a list and put in order — never typed.
  • Tick what the carrier speaks. Each codec shows what it costs the switch to convert and who can speak it; no browser speaks G.729, iLBC or G.726.
  • Order the ticked ones. Each call uses the first codec in this order that the other side also speaks, so the order is yours to choose: G.729 first saves bandwidth, G.711 first saves CPU. A newly ticked codec joins the end.
  • Nothing ticked means the default, G.711 A-law then µ-law — and the editor says so rather than leaving a blank.
The editor warns only when there is something to warn about: a list no browser can speak (every softphone call through the trunk is then transcoded — add G.711 behind it), a list with no G.711 at all, or — on a trunk pinned to a node — a ticked codec whose module that node has not loaded, where calls would quietly run in G.711 instead. Finding trunks by codec. The trunks list has a Speaks filter, and the API takes GET /trunks?codec=G729. Asking the carrier. A list is what you believe the carrier accepts. Test codecs, beside Verify on the trunk’s page, asks it: one call attempt per codec, offering that codec alone, hung up the moment the carrier rings and before anyone answers, so nothing is billed. A codec the carrier will not take comes back refused (488), which is the same INCOMPATIBLE_DESTINATION a customer’s call would have died of. Give it your own mobile: it rings once per codec for up to twenty seconds. A destination that answers without ringing first is hung up within a second, which some carriers bill as a one-second call. The API is POST /trunks/{id}/codec-test, one codec per call.

Direction

A trunk carries calls both ways unless you say otherwise, and that is what every trunk does until somebody changes it. A declaration is enforced, not decorative — that is the point of making it. On a carrier account an unexpected inbound path is how toll fraud arrives, so a call on a trunk you said would not receive one is refused rather than carried. It also stops FireTone telling you something untrue. An outgoing-only carrier has no use for ACL hosts, so Accepts no inbound is not shown for it and firetonectl doctor stops reporting it — a false alarm the check printed on every run until a trunk could say what it was.
It was a text box, and what was typed went to the switch as typed — which says nothing about a name it does not know. ULAW,PCMA offered PCMA alone; ALAW placed no call; PCMA@30I sent 20 ms packets, because a capital I is not a suffix. Lists are now stored in the one spelling the switch honours. A trunk saved before that keeps what was typed until it is next saved, and firetonectl doctor (which verify.sh runs) names any that need it.

Dial prefix

Digits this carrier wants in front of the number, added on the way out after the + is removed. Blank for none.
This is not the organisation’s outside-line prefix, which travels the other way: that is what staff dial to reach a line, and it is stripped on the way in. One word, two settings, opposite directions.

Number format

How numbers are sent to this carrier — the number dialled and the caller ID alike: National needs the carrier’s country. The dial prefix, if any, goes in front after formatting: prefix 1831 with national gives 18310412345678.

Caller ID

What the carrier is shown, chosen for each call in this order:
  1. the extension’s own caller ID number, when it is a real number (1001 is not; 0412 345 678 is, read in the extension’s country);
  2. the organisation’s Outbound caller ID — usually its main line;
  3. the trunk’s Default caller ID — usually the number the carrier account is registered as.
It is decided per carrier, so a call that fails over to a second trunk presents that trunk’s default rather than the first’s. When none of the three is a real number the call still goes out showing the extension number, and the log warns that the carrier is likely to refuse it.
Campaigns keep their own caller ID and are not affected by this chain.

When the carrier refuses a call

The caller hears busy for busy, and “the call cannot be completed as dialled” for anything else, instead of silence. The call’s page shows Far end — the carrier’s SIP answer and cause — and the log line names the trunk, the SIP status and the caller ID that was presented, which is usually the answer: a 403 straight after a caller ID change is the carrier refusing the number. sudo deploy/staging/verify.sh checks that every registering trunk on the box is actually registered with its carrier, and ftctl doctor finds a registering trunk that has lost its switch node.

Service levels and outbound rules

The whole of outbound routing — the order a call is decided in, the three kinds of rule, what Try a number means by a carrier being “passed over” — is on its own page: Outbound routing. What follows is the part that belongs to a carrier: its service levels.
Some carriers sell one account at several service levels, chosen per call by a tech prefix in front of the number. Voxbeam, for example: List them on the trunk’s page under Service levels. Then choose between them on Outbound routing, with outbound rules: a number pattern and the legs a matching call takes, in order.
  • The most specific pattern matching the number wins — +9192 beats +91 beats * — and only its legs are used. A +91 rule does not quietly fall back to the * rule’s legs.
  • A rule for one organisation beats a shared rule for the same pattern.
  • A leg’s level replaces the trunk’s dial prefix for that call. A leg with no level sends none.
  • A number no rule matches goes out only through the organisation’s own trunks, and is refused if it has none. It never goes to a shared carrier nobody chose. A rule for *, for all organisations or for one, is the default carrier.
  • A call fails over only to the legs its rule lists. A rule whose carriers are all disabled or unusable refuses the call rather than trying others.
  • Legs whose trunk is disabled are skipped, and ftctl doctor reports the rule (outbound-rule-unusable).
  • A leg whose carrier has a price list of its own that does not cover the number is passed over for the next leg. If no leg covers it, the call is refused and the log names the trunks.
Try a number on the same screen shows the legs a call to that number would take and the exact dial strings, from the same code a call runs. The call’s page and the call records show which level carried each call. A carrier that prices its levels differently can be priced that way: see a trunk’s own price list below.

Numbers a carrier sends without a +

A number arrives as +919216217231, 919216217231 or the national 09216217231. The first two are the same digits and match a DID without any configuration. The third only means something if you know the country. Switch on Accept national-format numbers, set the Country, and choose how a parsed number is accepted: regex is the escape hatch for a carrier whose numbering the library judges invalid. It still parses with the country’s rules — a pattern can test a number, it cannot convert one.
This is only ever consulted after the plain forms have failed to match, so switching it on cannot move a call that already works. It can only rescue one that was being refused.

Usage

Trunks → Usage compares carriers side by side: inbound and outbound counts, answered rate, minutes and spend, with a trunk selected breaking spend down by organisation. Every figure comes from the same place as the call list, so the two cannot disagree about what a call is.

A trunk’s own price list

On a trunk’s page, across the full width below Who may call in and Service levels, Destinations & pricing holds what that carrier can reach and what it charges: prefix, country, destination, currency, price per minute, connect fee, billing (minimum / increment, so 60/60 or 30/6) and, optionally, a service level. It does two things, and the second is the one that surprises people:
  1. It prices that trunk’s calls. A row for the call’s service level beats a row for every level; within each, the longest prefix wins. So Platinum and Gold on one carrier can cost what the carrier actually charges for them.
  2. It decides what the trunk can route. A carrier’s deck is also the list of where it can send a call. A number the list does not cover is not sent to that trunk: the next leg of the outbound rule is tried, and if no leg’s carrier lists the number the call is refused, with a log line naming the trunks and this screen.
The card says which of three states a trunk is in:

The table

The prices are listed in the card, paged, searched and sorted on the server — a deck is tens of thousands of rows and none of it is sent to the browser to be sorted there.
  • Search takes a number or a word. A number is matched from its first digit, with or without the +, so a dialled number pasted from a call record finds its destination (+1876… finds Jamaica; 44 does not find 1441). A word is matched anywhere in the destination, the country or the service level.
  • Filters: a service level — or Prices for every level — and a country.
  • Sorting: prefix (the default, which keeps a prefix beside its own per-level prices), destination, service level, and per minute — the last only on a list in a single currency. Across several it would order numbers, not money.
  • A price imported ahead of its date is listed with a from date badge; the one still in force sits on the row above it.
Add a price is a button. Everything done less often is in the Menu: Import…, Import history, Export, Quote a number… and Clear all…. A login that can see prices but not change them gets the table, the history, Export and the quote, and none of the rest. A trunk nobody has touched behaves exactly as it always did. Clearing a list hands the trunk back to the shared card; it does not become a trunk that can route nothing.

Importing a carrier’s deck

Menu → Import… takes the carrier’s CSV more or less as it arrives. Column names are matched loosely (prefix / code / dial_code, price / rate / cost, destination, currency, billing, service level / quality, effective from); prefixes may be written +91, 91 or 0091; a title line starting with # is skipped; and when there is no country column the country is worked out from the prefix (1876 is Jamaica, not the United States). A vendor’s rate sheet in its usual form is read as it stands: A sheet usually arrives before its date. Import it when it arrives. The new prices are listed as scheduled, today’s prices stay in force and stay listed until that moment, and What would a call cost? goes on quoting today’s price until then. A trunk whose whole list is scheduled behaves as it did before the import — it does not start refusing numbers five days early. A withdrawn destination stops being priced on the vendor’s date, and from then the trunk is not sent those numbers: the next carrier in the outbound rule takes them.
Expiry dates are not obeyed. A vendor’s “expires 3 October” means “expect another sheet”. If the list ended its prices then, a week with no new sheet would leave the trunk with no prices at all — and it would quietly stop being sent calls, or fall back to the shared card, because somebody was on holiday. Prices stay in force until a newer list replaces them.
An Excel workbook (.xlsx, .xls) is refused with the way to fix it: File → Save As → CSV UTF-8. Read as text a workbook is a zip file, and a complaint about missing columns would send you to rename columns that are already right. You choose what to assume when the file does not say — currency, minimum, increment, connect fee, service level, the date the prices take effect — and one of two modes: Preview first — the button says Preview until you have. It shows how many prices would be added, changed, left alone, removed and skipped, a few examples of old against new, any row it could not use (by line, with the reason), and any currency you have no exchange rate for.
A price with a seventh decimal place that is not zero is refused, not rounded. Storing 0.0123456 where the carrier said 0.01234567 is an under-charge on every minute of every call, and rounding it is a decision about your money an importer should not make for you.
A file with no usable rows is refused in either mode. In Replace it would otherwise remove every price and add none — and since a trunk with no list quietly falls back to the shared card, the mistake would not even look like one. A date in the future leaves today’s price in force until then. What a file does not mention, it does not change. A sheet dated Friday says nothing about today, so today’s prices stay until Friday — in either mode. And in Update, correcting today’s price for a destination leaves a price already scheduled for Friday alone. (Replace makes the file the whole list: a scheduled price it does not carry goes.) Clear all ends everything that has not ended, scheduled prices included.

Backup, and the same deck on another trunk

Export writes the whole list as the file the importer reads: every column, service levels by name, nothing tied to the trunk it came from. Its first line says which trunk, when, and how many prices.
  • Restore: import a trunk’s own export in Replace mode and the list comes back exactly — including mid-week, when it holds today’s prices, the ones scheduled for Friday and a withdrawal due with them. All three are in the file (a withdrawal to come is written the way a vendor writes one: a Deleted row on that date), and restoring a backup over itself changes nothing.
  • Reuse: import it into another trunk. A service level that trunk does not have is counted and shown in the preview, then left out or loaded for every level, as you choose — never quietly dropped or widened.
Import and export exist on the trunk and nowhere else. Download the current list first is offered inside both the import dialog and the Clear all confirmation.

Import history

Menu → Import history lists every import and every Clear all on the trunk, newest first: when, who, Update or Replace, the file’s name and size, and how many prices were added, changed, removed and left alone. Rows the importer skipped open out under the entry.
  • The file is kept. Download hands back exactly what was uploaded — the same bytes — which makes the history a backup of what each vendor actually sent, and the way to look again at a sheet from three months ago. Downloading needs the permission to change rates, where the list itself needs only the permission to see them: a vendor’s sheet can hold more than was imported.
  • An entry means it happened. It is written together with the prices, in one step. A preview is not in the history, nor is a file that was refused, and an import that failed part-way left neither prices nor an entry.
  • The newest twenty files on a trunk are kept, and a file over 16 MB once compressed is not kept at all. Either way the entry stays, saying which.
  • A price added or edited by hand is not here; that is in the audit log.

What would a call cost?

Menu → Quote a number… It is priced, per service level, by the same code that prices real calls: a price and the row that matched, no price, or not in this trunk’s list — calls are not sent here.

Rate cards

A rate card is a set of per-destination prices with an effective date. Cards come in two sides:
  • Cost — what you pay the carrier.
  • Sell — what the tenant pays you.
Both are matched by longest prefix, so a specific country beats a general one. A cost card attached to several trunks is the shared default. A trunk with a price list of its own uses that instead.

Prices are frozen at the call

A call is rated against the price that applied when it ran, and the rate id is recorded on the record. A price is never overwritten: changing one ends the old row and writes a new one, so the record still points at what the call was charged. Changing a price today does not restate last month’s invoices — which is the difference between a price change and an accounting scandal.

Money is exact

Amounts are exact decimals end to end, never floating point. A rate is a fraction of a cent and binary floating point cannot represent it exactly; a thousand calls is where that stops being theoretical.