inbound caller name & risk signal
Call numbers.online when an inbound call arrives to label it with a caller name (CNAM) and an optional risk tag before it reaches an agent or extension. There are two ways to wire it, depending on what your build can do:
| Path | Endpoint | Auth | You get | Best for |
|---|---|---|---|---|
| mod_cidlookup (simplest) | /v1/cid (plain text) | ?key= query param | A caller name, optionally spam-tagged | The fastest drop-in; no scripting |
| Lua dialplan (full) | /v1/lookup (JSON) | Authorization: Bearer header | CNAM and spam_score, line type, carrier; verified TLS | Routing on score, header auth, richer display |
Positioning. numbers.online provides a supplementary identity and risk signal. Your PBX keeps every routing, labelling, and blocking decision and remains responsible for compliance. The API never asserts a call is lawful, unlawful, "safe", or "spam" —
spam_scoreis a labelled, low-confidence signal. Whether any risk wording reaches a caller name is an operator opt-in (you choose the tag text and threshold).
Artifacts referenced here:
public/integrations/freeswitch/cidlookup.conf.xml — the mod_cidlookup configpublic/integrations/freeswitch/numbers_online_cnam.lua — the Lua dialplan scriptPOST https://numbers.online/v1/account/signup
{ "email": "[email protected]", "name": "Your Company" }
The key is returned exactly once — store it. Free tier: 60 requests/min, unbilled. Full docs: https://numbers.online/docs. Use a dedicated key for the PBX (see Security notes).
mod_cidlookup substitutes the inbound number into a URL and treats the HTTP
response body, verbatim, as the caller-ID name. Our /v1/cid endpoint always
returns text/plain — a name, an optional spam-tag prefix, or the literal
UNAVAILABLE — which is exactly what mod_cidlookup expects.
/v1/cid and not /v1/lookupX-API-Key
auth. The key must travel as the ?key= query param, which only /v1/cid
accepts./v1/cid returns plain text. /v1/lookup returns JSON, and mod_cidlookup would
paste a raw JSON blob (or an error body) into the caller name. Never point
mod_cidlookup at /v1/lookup.Copy cidlookup.conf.xml to conf/autoload_configs/cidlookup.conf.xml.
Set the url param's YOUR_API_KEY to your key, and country=US to your
default if bare national numbers are not NANP. In XML, & is written &:
<param name="url"
value="https://numbers.online/v1/cid/${caller_id_number}?key=YOUR_API_KEY&country=US"/>
The shipped config also sets cache=true, cache-expire=300,
curl-timeout=2000 (a 2-second fail-open cap). Reload:
fs_cli -x 'reload mod_cidlookup'
${caller_id_number} is the only substitution token mod_cidlookup offers; it
is the raw inbound number (often 10/11-digit national, not E.164). /v1/cid accepts
that loose format and resolves it via &country.
Risk wording is opt-in. Append to the url value (remember &):
&spam_tag=Spam%3F&spam_threshold=80
When a name scores at/above the threshold, the body is returned prefixed
(Spam? ACME CORP); with no name but a crossing score, the tag alone is returned.
Omit spam_tag entirely to never surface risk text in the caller name. Spam%3F
is the URL-encoded form of Spam?.
In Dialplan → Inbound Routes, edit the route and add ordered inline actions so the lookup runs before the bridge to an extension:
set caller_id_name=${cidlookup(${caller_id_number})} inline=true
set effective_caller_id_name=${caller_id_name} inline=true
In the FusionPBX action editor: Application = set, Data = the right-hand
side, and tick Inline. Place these above the existing bridge/transfer
action. The cidlookup API function reads the config you installed above.
Use this when you want the spam_score for routing, header-based auth, and verified
TLS. The script (numbers_online_cnam.lua) calls /v1/lookup (JSON) and sets:
| Channel variable | Meaning |
|---|---|
effective_caller_id_name | CNAM, optionally prefixed with your spam tag |
numbers_online_spam_score | 1–99 integer, for downstream routing |
numbers_online_cnam | the raw CNAM string (no tag prefix) |
mod_curl and the dialplan curl app cannot set an Authorization header
(open FreeSWITCH issue #1377). The script therefore shells out to the system curl
binary via io.popen with -H "Authorization: Bearer ..." and --max-time 2.
Copy numbers_online_cnam.lua to your mod_lua script directory
(conf/scripts/numbers_online_cnam.lua).
Add globals to conf/vars.xml — the key is never hardcoded in the script:
<X-PRE-PROCESS cmd="set" data="numbers_online_key=YOUR_API_KEY"/>
<!-- optional risk-tag opt-in -->
<X-PRE-PROCESS cmd="set" data="numbers_online_spam_tag=Spam?"/>
<X-PRE-PROCESS cmd="set" data="numbers_online_spam_threshold=80"/>
Leave numbers_online_spam_tag unset to never prefix the caller name with risk
text. Reload vars with fs_cli -x 'reloadxml'.
Confirm a system curl binary is on FreeSWITCH's PATH.
In Dialplan → Inbound Routes, add early in the route (before the bridge):
lua numbers_online_cnam.lua inline=false
Action editor: Application = lua, Data = numbers_online_cnam.lua, leave
Inline unticked. Downstream actions can branch on ${numbers_online_spam_score},
e.g. send a high score to an IVR challenge or voicemail instead of ringing through —
that routing decision is entirely yours.
If you run multiple tenants, issue a per-tenant sub-key through the MSP control plane so usage, billing, and rate limits are attributed per tenant:
POST https://numbers.online/v1/account/tenants # create a tenant
POST https://numbers.online/v1/account/tenants/{id}/keys # mint a per-tenant sub-key
Then template the per-domain key into that domain's cidlookup.conf.xml url, or
set a domain-scoped numbers_online_key global, so each FusionPBX domain dips with
its own key. Never share one key across tenants you bill separately.
?key=), which can land in proxy, gateway, or access logs. Treat it as
low-trust: use a dedicated key for the CID endpoint and rotate it on a
schedule. The Lua path avoids this by sending the key in a header.CURLOPT_SSL_VERIFYPEER=0,
so although the request is HTTPS, the certificate is not validated against a CA.
This is a mod_cidlookup limitation, not a numbers.online setting. The Lua path
uses the system curl, which verifies TLS normally — prefer it where peer
authentication matters.Caller-name lookup sits on the call-setup path, so it must never block or delay a call:
curl-timeout/--max-time tight — 2s recommended, 5s max.UNAVAILABLE (Path 1) or null fields (Path 2),
never an error you must handle on the call leg.One mod_cidlookup caveat to know about (Path 1 only). mod_cidlookup uses the raw
HTTP response body as the caller name and only rejects the literal UNKNOWN /
UNAVAILABLE sentinels. The numbers.online API itself never returns anything but
plain text on this endpoint — but infrastructure in front of it can: if the
service is unreachable, Cloudflare may answer with an HTML error page faster
than your curl-timeout fires, and mod_cidlookup would briefly display that markup
as a caller name. This is inherent to how mod_cidlookup consumes responses, and it
affects every URL-based CNAM source the same way. If that residual risk matters to
you, use the Lua path instead: the script pattern-matches the JSON response and
treats anything that doesn't look like a lookup result — including any HTML error
page — as "no data" (fail open, channel untouched).
| Event | Price |
|---|---|
| Lookup with a fresh CNAM dip | $0.004 |
| Cache hit, or no CNAM supplier | $0.002 |
| Invalid number | free |
Free tier is unbilled at 60 requests/min. Caching (cache-expire / max_cache_age)
directly trims cost on repeat callers.
Docs: https://numbers.online/docs. For higher rate limits, paid tiers, or multi-tenant onboarding: [email protected].