# Voxtelesys Integration for ERPNext / 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 overview ### Direct API path ``` 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) ``` --- ## Installation ```bash cd ~/frappe-bench bench get-app https://forge.wilddragon.net/zgaetano/voxtelesys_integration.git bench --site erp.broadcastmgmt.cloud install-app voxtelesys_integration bench --site erp.broadcastmgmt.cloud migrate bench restart ``` After install, three custom fields are added via fixtures: - `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 --- ## Configuration (direct API path) 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]` - `called_did` = `[called_did]` (optional) - `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. --- ## Usage ### 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 cd ~/frappe-bench/apps/voxtelesys_integration git remote -v # should point at forge.wilddragon.net bench --site erp.broadcastmgmt.cloud clear-cache && bench restart ``` To export updated fixtures after editing custom fields in the UI: ```bash bench --site erp.broadcastmgmt.cloud export-fixtures --app voxtelesys_integration ``` --- ## License MIT