# FreeSWITCH & FusionPBX caller ID lookup — CNAM + inbound risk signal

- Category: PBX & softphones
- Published: June 3, 2026 · Updated: June 15, 2026
- Canonical: https://numbers.online/docs/integrations/freeswitch-fusionpbx

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_score` is 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 config
- `public/integrations/freeswitch/numbers_online_cnam.lua` — the Lua dialplan script

## Get an API key

```
POST https://numbers.online/api/v1/account/signup
{ "email": "you@example.com", "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](#security-notes)).

---

## Path 1 — mod_cidlookup (simplest)

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.

### Why `/v1/cid` and not `/v1/lookup`

- mod_cidlookup **cannot send HTTP headers**, so it cannot use Bearer/`X-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`.

### Install

1. Copy `cidlookup.conf.xml` to `conf/autoload_configs/cidlookup.conf.xml`.
2. 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 `&amp;`:

   ```xml
   <param name="url"
     value="https://numbers.online/api/v1/cid/${caller_id_number}?key=YOUR_API_KEY&amp;country=US"/>
   ```

3. 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`.

### Optional spam tag

Risk wording is **opt-in**. Append to the `url` value (remember `&amp;`):

```
&amp;spam_tag=Spam%3F&amp;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?`.

### FusionPBX GUI wiring

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.

---

## Path 2 — Lua dialplan (full JSON + header auth + spam score)

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) |

### Why Lua and not mod_curl

`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`.

### Install

1. Copy `numbers_online_cnam.lua` to your mod_lua script directory
   (`conf/scripts/numbers_online_cnam.lua`).
2. Add globals to `conf/vars.xml` — the key is **never** hardcoded in the script:

   ```xml
   <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'`.
3. Confirm a system `curl` binary is on FreeSWITCH's `PATH`.

### FusionPBX GUI wiring

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.

---

## Multi-tenant (FusionPBX domains / MSPs)

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/api/v1/account/tenants          # create a tenant
POST https://numbers.online/api/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.

## Security notes

- **Query-param key visibility (Path 1).** With mod_cidlookup the key rides in the
  URL (`?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.
- **mod_cidlookup disables TLS verification.** It sets `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.
- **Never log raw numbers or keys.** The shipped artifacts keep both out of stdout
  and logs; keep it that way in any local customisation.

## Fail-open guidance

Caller-name lookup sits on the **call-setup path**, so it must never block or delay
a call:

- Keep `curl-timeout`/`--max-time` tight — **2s recommended, 5s max**.
- On any timeout or error, both paths proceed with the call **unchanged**:
  mod_cidlookup yields no name; the Lua script returns without touching the channel.
- Invalid or unknown numbers return `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).

## Pricing

| 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.

## Where to get help

Docs: <https://numbers.online/docs>. For higher rate limits, paid tiers, or
multi-tenant onboarding: **api@numbers.online**.

## Keys & references

Keys are self-service at https://numbers.online/docs — shown once, stored only as a hash.
Machine-readable schema: https://numbers.online/api/spec · all guides: https://numbers.online/md/docs/integrations

- Previous guide: [FreePBX & Asterisk caller ID lookup](https://numbers.online/md/docs/integrations/asterisk-freepbx)
- Next guide: [3CX CRM integration](https://numbers.online/md/docs/integrations/3cx)

---

Numbers Online — community-powered phone-number trust signals. Every figure on this
page is a supplementary, low-confidence signal — advisory, not a verdict on any number
or its owner.

- Canonical page (HTML): https://numbers.online/docs/integrations/freeswitch-fusionpbx
- Report a call: https://numbers.online/report · Listed in error? Request removal: https://numbers.online/opt-out
- Machine guide to the phone-intelligence API: https://numbers.online/llms.txt · OpenAPI: https://numbers.online/api/spec
