Feature request: SIP/PBX inbound call integration — auto-populate caller info on incident intake (999-style ANI)

73 views
Skip to first unread message

Khalaf Almazrouei

unread,
Aug 15, 2026, 4:57:18 PMAug 15
to Open Source CAD

Hope everyone is doing well. Building on the DMR/voice work, I'd like to propose a call-taking integration.

The idea: when an inbound call arrives on the dispatch line (via a SIP PBX such as 3CX, FreePBX/Asterisk, or a Grandstream UCM), TicketsCAD pops a call-intake screen with fields pre-filled from the call itself — the way ANI works in a real 999/911 center:

  • Caller number (from SIP INVITE caller ID) auto-filled into the incident contact field
  • Repeat-caller recognition: look the number up against previous incidents/constituents; if known, show name, address on file, and prior incident history alongside the new intake form
  • One-click "create incident from call" with timestamp, line/queue, and recording reference attached
  • Optionally: call state in the UI (ringing/answered/ended), and linking the PBX call recording to the incident record

Integration paths, simplest first:

  1. Generic webhook receiver — an api/call-event.php that accepts POSTs (number, direction, call-id, timestamp). Nearly every PBX can emit this: 3CX has Call Control API/webhooks, Asterisk/FreePBX via AMI or dialplan curl, Grandstream UCM via its API. This keeps TicketsCAD PBX-agnostic — no SIP stack needed in the CAD at all.
  2. AMI/ARI listener for Asterisk-family PBXs for richer call state.
  3. Full SIP registration by the CAD itself — probably overkill; the webhook route covers the dispatch use case.

The CRM half is mostly already there: constituents exist, incidents have contact fields — this adds a lookup keyed on phone number plus a screen-pop.

I can prototype the PBX side: I run a 3CX V20 instance and a Grandstream UCM6302A with an analog FXO line, so I can generate real inbound-call webhooks for both and document the payloads if a receiver endpoint shape is agreed.

Eric Osterberg

unread,
Sep 6, 2026, 6:25:16 PMSep 6
to open-so...@googlegroups.com
Hi Khalaf,

Following up on this one — it's built and shipped now, as of TicketsCAD v4.2.25.

The shape ended up very close to what you proposed. A small external adapter process talks to your PBX (Asterisk/FreePBX over AMI, or a generic webhook receiver for hosted SIP trunk providers like 3CX or Grandstream UCM setups) and normalizes whatever your system sends into one canonical event shape, which it then posts to TicketsCAD over a bearer-token-authenticated endpoint. TicketsCAD itself never speaks SIP, AMI, or ARI directly — same pattern as the DMR bridge, one adapter per external system rather than the CAD trying to understand every PBX's native API.

What you get on the CAD side: every logged-in, permission-holding dispatcher sees a live ring notification the moment a call comes in, with a distinct ring tone. The first person to click Answer atomically claims it — no double-dispatch race — and gets a New Incident form in a new tab, pre-filled with the caller's number and, once claimed, their prior incident history.

On the repeat-caller lookup and RBAC question I flagged before — that got a real answer. There are two dedicated field-level permissions, one for caller history and one specifically for patient history, so an install can grant "can see this caller's past incidents" without also granting "can see their medical history." Five new permissions total cover the whole feature: who can see the call queue, who can claim a call, who can override an active claim or configure trunks, and the two field-level ones.

A few things worth knowing if you want to try it: claiming is a genuinely atomic database operation, so simultaneous clicks from two dispatchers can't both win. If the CAD's claim and the physical phone answer disagree — common on real PBX setups where only one extension can actually pick up — any qualified dispatcher can self-correct within a short configurable grace window, no supervisor approval needed for that first correction. It's keyboard-first too: arrow keys cycle between simultaneous calls, A claims, T reassigns or reclaims, Escape dismisses locally. Missed calls land in a collapsible panel instead of disappearing.

Admin setup is under Settings, Communications and Integrations, Inbound Calls — create a trunk there and you'll get a bearer token shown once, which is what you point your adapter at. The reference adapter is in the repo under services/sip-bridge, with both an Asterisk AMI mode and a generic webhook mode you could adapt directly for 3CX or Grandstream.

Since you offered to generate real inbound-call webhooks from your own 3CX and Grandstream setups — that's still genuinely useful, even with the feature already built. The generic webhook mode has one worked example adapter function in the reference code; a second one written against a real 3CX or Grandstream payload shape (rather than a guess) would harden it for anyone else running those systems. If you get a chance to try it against your own trunk and hit anything that doesn't fit, I'd want to hear about it.

73,
Eric

--
You received this message because you are subscribed to the Google Groups "Open Source CAD" group.
To unsubscribe from this group and stop receiving emails from it, send an email to open-source-c...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/open-source-cad/e30c02db-d7ee-47aa-8582-3730c87100e5n%40googlegroups.com.

Justin Gilbert

unread,
Sep 17, 2026, 5:15:27 AMSep 17
to open-so...@googlegroups.com
Hi Eric & Khalaf,

We also run 3CX, I am trying to set this up for testing but I am at a bit of a roadblock after minting the token in Tickets. I am not quite sure what next steps would be exactly to get this working.

Eric Osterberg

unread,
Oct 3, 2026, 2:12:12 PMOct 3
to open-so...@googlegroups.com
Hi Justin,

Sorry for the slow reply, and thanks for trying this on 3CX.

Short version: minting the token only creates the credential. A small program, the bridge in services/sip-bridge, has to run somewhere that can reach both your 3CX and your TicketsCAD server and report calls to TicketsCAD using that token. Nothing happens until that program is running.

The 3CX side of that program did not exist when you wrote. It does now: the bridge has a real 3CX mode, and it is in the main TicketsCAD repository as of today. If you update with git pull and run php sql/run_migrations.php you have it; no release is needed. A few things are worth knowing.

First, a question that decides whether it can help you at all: which 3CX edition do you run? 3CX only exposes live call events (ringing, answered, ended, missed) through its Call Control API, and 3CX documents that API as part of the AI Edition (the edition that used to be called Enterprise). PRO, Basic and the free tier do not include it. A quick check: in the 3CX Admin Console, is there an Integrations > API entry? If there is not, tell me which edition you have and what you do see under Integrations, because that decides which adapter gets built next.

If you do have it, setup will be: create an API client in Integrations > API (tick 3CX Call Control API Access, and list every dispatch extension under the extensions to monitor, since 3CX sends nothing at all for an empty list), copy the API key (3CX shows it once), install Python with requests and websockets, and fill in a bridge.ini. The Inbound Calls page in TicketsCAD (Settings, Communications and Integrations, Inbound Calls) now has a Setup button that generates that file with your token already in it, shows a live "Bridge connected" badge once the bridge is running, and has a Send test call button so you can prove the TicketsCAD side works before the phone system is involved. The bridge also has a --check command that names the exact setting at fault if something is wrong.

One honest caveat: I built this from 3CX's documentation and from call captures other 3CX users have posted, and tested it against a simulator, not a live 3CX. When you try it, the bridge can record real 3CX traffic to a file (phone numbers, names and addresses are masked automatically). If you are willing to place a few test calls (one answered, one left to ring out, one through your queue or ring group) and send me that file, it will let me confirm the details I could not check without a real system.

73,
Eric

Khalaf Almazrouei

unread,
Oct 3, 2026, 4:52:23 PMOct 3
to Open Source CAD

Hi Eric and Justin,

Hope you're both doing well, and sorry for the late reply. Work has kept me busy.

I haven't tested the 3CX bridge yet. I'm setting up a new server and will run the latest TicketsCAD in Docker. It's been a while since I tested a current version, so I'm reinstalling from scratch. I'll try to match a real dispatch setup as closely as I can so I can give you useful feedback on how easy it is to use.

On your edition question: I'm on the 3CX AI Edition (V20, self-hosted), so the Call Control API should be available to me.

I'll do my best to test everything thoroughly and will update the thread with my results.

73,
Khalaf

Eric Osterberg

unread,
Oct 8, 2026, 11:12:25 PMOct 8
to Open Source CAD
Hi Khalaf,

Thanks, that is good news. AI Edition V20, self-hosted, is exactly the edition the bridge needs, so you should be able to get it working end to end.

A few things that will save you time with a Docker install:

1. The bridge is not part of the Docker containers. It is a small Python program you run on any computer that can reach both your 3CX web address and TicketsCAD. The 3CX server itself is fine, and so is the Docker host. It needs Python 3 and pip install requests websockets, and it only makes outbound connections.

2. Take the bridge from the same copy of TicketsCAD you are installing (the services/sip-bridge folder). It is at version 1.1.1 as of this week, and the first line of its --check output shows the version, so you will know you have the current one.

3. The TicketsCAD address. The Setup window on the Inbound Calls page fills it in from the address in your browser. With the default Docker setup that is https://www.google.com/url?q=http://localhost:8081&source=gmail&ust=1791505284695000&sa=E, which only works if the bridge runs on the Docker host itself. If you run the bridge on the 3CX server or another computer, change it in the Setup window to the Docker host's address, for example https://www.google.com/url?q=http://192.168.1.50:8081&source=gmail&ust=1791505284695000&sa=E, with nothing after the port.

4. Before starting the bridge for real, run python3 bridge.py --config bridge.ini --check. It checks TicketsCAD first, then your 3CX credentials and the extensions it can see, and names the setting at fault. Two 3CX settings catch most people: tick 3CX Call Control API Access, and add every dispatch extension under Extensions to monitor. An empty list means no events at all, not all extensions.

5. The full walkthrough is in docs/INBOUND-SIP-CALLS.md, under "3CX, step by step".

The request from my earlier note still stands: if you set capture_file in bridge.ini and place a few test calls (one answered, one left to ring out, one through a queue or ring group), that file would let me confirm the details I could only check against a simulator. Phone numbers, names and device addresses are masked by default.

73,
Eric

Khalaf Almazrouei

unread,
Oct 17, 2026, 3:00:40 PM (9 days ago) Oct 17
to Open Source CAD

Hi Eric,

Sorry for the slow reply. I’ve been tied up with work and clinical rotations, so I couldn’t get back to this sooner.

First, an issue in the Docker setup: the scheduled job that clears old inbound phone call entries isn’t set up in the Docker containers. In a non-Docker install, cron usually takes care of it. In Docker nothing runs it, so inbound call records keep piling up and can flood the Inbound Calls page and the database over time. I worked around it on my side, and it may be worth adding to the Docker image or the docs.

How I set it up on Docker with Coolify (for anyone else integrating):

  1. Deploy TicketsCAD in Coolify
    • Create a new resource from the TicketsCAD repository using the Docker Compose build.
    • Assign a domain (for example https://cad.example.com). Coolify’s proxy handles HTTPS, so there’s no need to expose port 8081 publicly.
    • Deploy and confirm the web interface loads.
  2. Add the inbound call cleanup as a scheduled task
    • In Coolify, open the TicketsCAD resource and go to Scheduled Tasks, then Add.
    • Name: inbound-calls-cleanup
    • Command: [cleanup command]
    • Frequency: [e.g. */5 * * * *]
    • Container: the TicketsCAD app container (not the database).
    • Save, then use Execute Now once and check the logs to confirm it runs cleanly.
    • Without Coolify, the same thing works from the host crontab:
      */5 * * * * docker exec <container> [cleanup command]
  3. Set up the 3CX bridge
    • Copy services/sip-bridge from the same TicketsCAD copy to a machine that can reach both 3CX and TicketsCAD. I used the 3CX server.
    • Run pip install requests websockets.
    • In the Inbound Calls Setup window, set the TicketsCAD address to the public domain (https://cad.example.com, nothing after it), not localhost.
    • In 3CX, tick 3CX Call Control API Access and add every dispatch extension under Extensions to monitor.
    • Run python3 bridge.py --config bridge.ini --check and fix whatever setting it names before starting the bridge for real.

Next steps on my side: I’ll update to bridge 1.1.1 and test the new version. I’ll also set capture_file and place the three test calls you asked for: one answered, one left to ring out, and one through a queue or ring group. I’ll send you the capture file and the --check output.

73,
Khalaf

Eric Osterberg

unread,
Oct 19, 2026, 8:23:25 PM (7 days ago) Oct 19
to open-so...@googlegroups.com
Hi Khalaf,

No apology needed, and thank you for the write-up. The Coolify steps will help the next person.

You were right about the scheduled job, and it was wider than that one job: the Docker setup had no scheduler at all, so none of TicketsCAD's background jobs ever ran in Docker. That includes the one that moves finished calls out of wrap-up on the Inbound Calls page, and also scheduled-call activation, the PAR roll-call, the held-message sweep, webhook retries and the log retention purges. The automatic backups only ran when someone happened to load a page.

That is fixed on main in the GitHub repository as of today. docker-compose.yml now has a "scheduler" service that runs every job at its own interval from the same image as the app. It waits for the app to finish installing or migrating the database, and it never runs two copies of a job at once. If Coolify deploys the repository's docker-compose.yml, pulling the update and redeploying brings the scheduler up on its own, and Settings > System Health shows when each job last ran.

If you would rather keep a Coolify scheduled task instead, set it to run every minute in the app container with this command:

su -s /bin/sh www-data -c "php /var/www/html/tools/run-scheduled-jobs.php --once"

It runs every job that is due. One of the two is enough, and if both are present a job still never runs twice. Work that piled up while nothing was running is not replayed as if it were new: anything more than an hour overdue is recorded as expired instead. The details are in docs/DOCKER.md, section 4a, "Background jobs".

I look forward to the capture file and the --check output from bridge 1.1.1.

73,
Eric

--
You received this message because you are subscribed to the Google Groups "Open Source CAD" group.
To unsubscribe from this group and stop receiving emails from it, send an email to open-source-c...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages