From 78f69d8a88b07cea745513bea74de1ba2008c2e7 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Tue, 12 May 2026 00:14:28 -0400 Subject: [PATCH] v2.1: docs + boot_session sms_enabled + legacy api header: README.md --- README.md | 188 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 139 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 743f068..996a19b 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,36 @@ -# Voxtelesys Integration for ERPNext Helpdesk +# Voxtelesys Integration for ERPNext / Frappe Helpdesk -Minimal Frappe app that receives an HTTP webhook from **3CX** when an inbound -call arrives (via a Voxtelesys SIP trunk) and automatically creates an -**HD Ticket** in Frappe Helpdesk. +Frappe app that wires the **Voxtelesys** voice + SMS platform into ERPNext — +giving Helpdesk agents click-to-call, inbound call popups, agent routing, +call recording, SMS send/receive, and auto-ticketing on missed calls. + +Two architectures are supported and can run side by side: + +| Path | Use when | Endpoint | +| --- | --- | --- | +| **Direct API** (v2.x) — ERPNext talks to Voxtelesys directly | You want click-to-call, agent routing, SMS, and recording driven from the desk UI | `api.voxtelesys.handle_inbound_call`, `api.sms.handle_inbound_sms` | +| **3CX bridge** (v1.x, legacy) — 3CX brokers calls, ERPNext just auto-creates tickets | Your 3CX PBX already routes inbound calls and you only want ticketing on the ERPNext side | `api.inbound_call` | --- -## Architecture +## Architecture overview +### Direct API path ``` -Voxtelesys SIP trunk → 3CX PBX → CF_URLFetch (Call Flow) → ERPNext webhook → HD Ticket +Caller ── PSTN ──→ Voxtelesys ──webhook──→ ERPNext + │ + ├─ writes Voxtelesys Call Log + ├─ creates HD Ticket on missed call + ├─ rings agent (Round Robin / Availability) + └─ returns VoXML → call connects to agent's phone +``` + +### 3CX bridge path +``` +Caller ── PSTN ──→ Voxtelesys SIP trunk ──→ 3CX PBX + │ + ├─ rings extensions normally + └─ CF_URLFetch ─→ ERPNext (creates HD Ticket only) ``` --- @@ -24,67 +45,136 @@ bench --site erp.broadcastmgmt.cloud migrate bench restart ``` ---- +After install, three custom fields are added via fixtures: -## Webhook Endpoint - -``` -POST https://erp.broadcastmgmt.cloud/api/method/voxtelesys_integration.api.inbound_call -``` - -### Parameters (POST body or query string) - -| Parameter | Required | Description | -|---|---|---| -| `caller_id` | Yes | Inbound caller number e.g. `+12025551234` | -| `called_did` | No | DID that was dialled | -| `call_id` | No | 3CX internal call ID (for deduplication) | - -### Response - -```json -{ "ok": true, "ticket": "HD-TICKET-00001" } -``` +- `User.custom_voxtelesys_number` — E.164 number to ring for inbound routing +- `HD Ticket.custom_voxtelesys_call_sid` — dedup for direct-API missed-call tickets +- `HD Ticket.custom_3cx_call_id` — dedup for 3CX-bridge tickets --- -## 3CX Call Flow Setup +## Configuration (direct API path) -1. Open **3CX Management Console → Call Flow Designer** -2. Create a new Call Flow (or edit your inbound DID's flow) -3. Add a **CF_URLFetch** block with these settings: +1. **Get a Voxtelesys API token** from the [Voxtelesys developer portal](https://developer.voxtelesys.com/apis/authorization/). +2. Open **Voxtelesys Settings** in ERPNext and fill in: + - **Voxtelesys API Token** (Bearer token) + - **Default Caller ID** (E.164, used for outbound voice and SMS) + - **Public Base URL** (e.g. `https://erp.broadcastmgmt.cloud`) + - **Webhook Signing Secret** (optional but recommended — HMAC-SHA256) + - **Enable Call Recording** if desired + - **Inbound Call Routing** (`Round Robin`, `Availability-Based`, or `Direct (no routing)`) +3. Copy the two **Webhook URLs** shown in Voxtelesys Settings and paste them into: + - Voice API Profile → Answer URL: `…/api/method/voxtelesys_integration.api.voxtelesys.handle_inbound_call` + - SMS Profile → Inbound Webhook: `…/api/method/voxtelesys_integration.api.sms.handle_inbound_sms` +4. Tick **Enabled** and save. +5. Test with the **Test API Connection** button on Voxtelesys Settings. + +### Agent routing setup + +For Round Robin / Availability-Based routing to work, each agent's **User** +record needs a `Voxtelesys Number` set (the field is added automatically by +the fixture). This is the actual phone number Voxtelesys will dial — usually +the agent's cell or desk phone DID, in E.164. + +--- + +## Configuration (3CX bridge path) + +1. **3CX → Management Console → Call Flow Designer** +2. Create or edit the Call Flow for your inbound DID +3. Add a **CF_URLFetch** block: - **URL**: `https://erp.broadcastmgmt.cloud/api/method/voxtelesys_integration.api.inbound_call` - **Method**: POST - **Parameters**: - - `caller_id` = `[caller_id]` (3CX variable for inbound caller number) + - `caller_id` = `[caller_id]` - `called_did` = `[called_did]` (optional) - - `call_id` = `[call_id]` (optional, enables deduplication) -4. Connect the block in your call flow — it can run in parallel with ringing, no need to wait for the response + - `call_id` = `[call_id]` (recommended — enables dedup) +4. Optional secret token: `bench --site … set-config voxtelesys_webhook_secret yourtoken` and append `?secret=yourtoken` to the URL. --- -## Optional: Webhook Secret +## Usage -To prevent unauthorized calls to the endpoint, add this to your site config: +### Click-to-call + +- **HD Ticket form**: a "Voxtelesys → Call ..." button appears in the + toolbar whenever the ticket's linked Contact has a mobile / phone number. +- **Lead / Contact forms**: same button group; works on any Phone field via + the `frappe.phone_call.handler` override. +- A floating call bar appears at the bottom of the screen showing live call + status. Clicking "Open Log" jumps to the Call Log entry. + +### Inbound calls + +When a call hits the inbound webhook, the agent on shift gets a popup with +the caller's number and a "Ringing…" indicator. If no agent answers, a +**missed-call HD Ticket** is auto-created (toggle via Voxtelesys Settings → +*Auto-create Helpdesk Ticket on Missed Call*). + +### SMS + +- **Send**: "Voxtelesys → Send SMS" button on HD Ticket and Lead forms. +- **Receive**: inbound MO messages from a known Contact with an open HD + Ticket are automatically appended as a `Communication` row on the ticket + thread (toggle via Voxtelesys Settings → *Auto-attach Inbound SMS to Open + Ticket*). +- All messages are persisted to the **Voxtelesys SMS Log** doctype with + Dynamic Link entries for cross-referencing. + +--- + +## DocTypes + +| Name | Purpose | +| --- | --- | +| **Voxtelesys Settings** | Single — global config | +| **Voxtelesys Call Log** | One row per call (in or out) with status, duration, recording URL, links | +| **Voxtelesys SMS Log** | One row per SMS (in or out) with body, status, delivery receipt, links | + +--- + +## Scheduled jobs + +- `sync_pending_call_logs` runs every 5 minutes to reconcile any Call Log + rows stuck in `Ringing` or `In Progress` because a status webhook was + missed. Configured in `hooks.py:scheduler_events`. + +--- + +## Whitelisted API surface + +| Method | Auth | Purpose | +| --- | --- | --- | +| `voxtelesys_integration.api.voxtelesys.handle_inbound_call` | guest | Voxtelesys voice webhook | +| `voxtelesys_integration.api.voxtelesys.handle_call_status` | guest | Voxtelesys status callback | +| `voxtelesys_integration.api.voxtelesys.make_outbound_call` | user | Click-to-call from desk UI | +| `voxtelesys_integration.api.voxtelesys.test_connection` | sysmgr | Settings → Test button | +| `voxtelesys_integration.api.voxtelesys.get_linked_call_logs` | user | Form sidebar history | +| `voxtelesys_integration.api.sms.send_sms` | user | Send SMS from desk UI | +| `voxtelesys_integration.api.sms.handle_inbound_sms` | guest | Voxtelesys SMS webhook | +| `voxtelesys_integration.api.sms.get_linked_sms_logs` | user | Form sidebar SMS history | +| `voxtelesys_integration.api.inbound_call` | guest | Legacy 3CX CF_URLFetch entrypoint | + +Guest endpoints verify a HMAC-SHA256 signature in the +`X-Voxtelesys-Signature` header against the **Webhook Signing Secret** in +settings (if configured). The legacy 3CX endpoint uses a query-string +`?secret=` token from `site_config.json` instead. + +--- + +## Development ```bash -bench --site erp.broadcastmgmt.cloud set-config voxtelesys_webhook_secret "yourtoken" +cd ~/frappe-bench/apps/voxtelesys_integration +git remote -v # should point at forge.wilddragon.net +bench --site erp.broadcastmgmt.cloud clear-cache && bench restart ``` -Then append `?secret=yourtoken` to the 3CX webhook URL. +To export updated fixtures after editing custom fields in the UI: ---- - -## Optional: Custom Field for Deduplication - -To prevent duplicate tickets if 3CX retries the webhook, add a custom field -to HD Ticket: - -- **DocType**: HD Ticket -- **Field Name**: `custom_3cx_call_id` -- **Field Type**: Data - -This can be done via **ERPNext → Customize Form → HD Ticket**. +```bash +bench --site erp.broadcastmgmt.cloud export-fixtures --app voxtelesys_integration +``` ---