# Project Proposal — GoHighLevel ↔ Twilio ↔ 3CX Call Bridge

**Prepared for:** [Client Name]
**Prepared by:** Hexatech Solution
**Contact:** dev@hexatechsolution.com
**Date:** 2026-05-08
**Document version:** 1.0

---

## 1. Executive Summary

This proposal covers the design, build, and deployment of a custom voice integration that routes inbound customer calls from your **GoHighLevel (GHL)** support number through **Twilio** as the carrier and into your **3CX PBX** as the call-distribution engine — while keeping every call event, contact, tag, note, and recording fully synchronised back into your GHL CRM.

The result is a single, unified workflow:

> A customer calls your published support number → the call is delivered to a CSR on the 3CX desktop or mobile app → the call is automatically logged against the customer's GHL contact (created if new) with status, duration, recording, tags, and any follow-up automation triggered.

No CSR has to manually log a call. No customer is sent to voicemail unless you want them to be. No call ends up untracked in the CRM.

---

## 2. The Problem

Out of the box, GoHighLevel's hosted phone system does not expose a way to route inbound calls to a third-party PBX (3CX) and have the call still appear inside the GHL contact timeline as a properly attributed call record.

The typical alternatives each break something important:

| Approach | What breaks |
|---|---|
| Use GHL's built-in calling only | No queueing, no CSR multi-tasking, no advanced ring strategies, no 3CX features (call park, transfer, BLF, internal chat, supervisor barge-in). |
| Use 3CX with an external SIP trunk only | CRM has zero knowledge of inbound calls. CSRs manually create contacts and log notes — slow, error-prone, and incomplete. |
| Forward GHL → 3CX directly via PSTN | CRM logging is partial or silent. No tags, no workflows, no missed-call automations. |

You need both: 3CX's mature call-handling and GHL's CRM, contacts, and automation — connected end-to-end, in real time.

---

## 3. The Solution

A lightweight PHP middleware ("the bridge") that sits between Twilio and 3CX and synchronises every call event with GHL via Twilio-format webhooks and the GHL REST API.

### 3.1 Call Flow

```
Customer dials your GHL support number
        │
        │  GHL "Forward Call" → Twilio DID
        ▼
Twilio receives the call
        │
        │  Twilio POSTs to /twilio/inbound.php
        ▼
Bridge (this system)
        │
        │  1. Validates Twilio signature (HMAC-SHA1)
        │  2. Logs the inbound event
        │  3. Returns TwiML: <Dial answerOnBridge="true"><Sip> → 3CX queue
        ▼
3CX queue 800 (round-robin, "keep in queue", hold music)
        │
        ▼
CSR rings on 3CX desktop / mobile app and answers
        │
        │  Twilio POSTs status to /twilio/status.php on every leg event
        ▼
Bridge (status handler)
        │
        │  1. Validates signature
        │  2. Idempotency check (one CRM write per CallSid)
        │  3. Calls GHL REST API:
        │       - Upsert contact by phone number (E.164)
        │       - Add/remove tags (Support Call, Missed Call, etc.)
        │       - Append a structured call note (status, duration,
        │         agent, recording URL)
        │       - Trigger a GHL workflow (missed-call SMS, etc.)
        ▼
GHL CRM — contact, timeline, tags, workflows fully updated
```

### 3.2 What the customer experiences

- They dial the same number you've always advertised.
- They hear hold music (3CX) instead of awkward silence while queued.
- They are connected to the next available CSR — round-robin, no busy tone.
- If no agent is available within the configured wait window, a missed-call SMS is automatically dispatched from GHL ("Sorry we missed your call! We'll be in touch shortly.").

### 3.3 What the CSR experiences

- They use 3CX desktop or mobile, exactly as before.
- The original caller's number is presented (not the Twilio DID), so they know who's calling.
- Calls are distributed via 3CX's queue (round-robin by default; configurable).
- Concurrent calls are queued, never blocked.

### 3.4 What the business / CRM experiences

- Every inbound call appears in GHL against the correct contact (created on the fly if new).
- Status, duration, and recording URL are persisted to the contact note.
- Tags are added/removed atomically: `Support Call`, `Support Call Answered`, `Missed Call`.
- Workflows (missed-call SMS, answered-call follow-up, satisfaction survey, etc.) trigger automatically.

---

## 4. Architecture & Components Delivered

| Component | Role |
|---|---|
| `twilio/inbound.php` | Receives Twilio's inbound webhook, validates the signature, logs the call, returns TwiML that bridges to the 3CX SIP queue. |
| `twilio/status.php` | Handles Twilio's `<Dial action>` end-of-call callback **and** per-leg `<Sip statusCallback>` events. Performs idempotent CRM enrichment. |
| `ghl/GHLClient.php` | REST API client: contact upsert by phone, add/remove tags, append call notes, trigger workflows. |
| `ghl/GHLNotifier.php` | Optional Twilio-format notifier for GHL's hosted endpoints (used by the alternative Telnyx flow). |
| `twilio/TwilioValidator.php` | HMAC-SHA1 signature validator for Twilio webhook authenticity. |
| `webhook/3cx_events.php` | Optional 3CX-side webhook receiver for richer agent-level data. |
| `config/config.php` + `.env` | Single source of truth for credentials, numbers, queue parameters, workflow IDs. |
| `test/*` | Pre-flight: connection verification, GHL endpoint test, simulated Twilio call, simulated 3CX events. |
| `logs/bridge.log` + `logs/processed/` | Append-only audit log + per-`CallSid` idempotency markers. |
| `nginx.conf.example` | Reverse-proxy + TLS termination template. |

### 4.1 Hardening Already Built In

- **Signature validation** on every Twilio webhook (HMAC-SHA1 of the URL + sorted POST params).
- **Idempotency by `CallSid`** — Twilio fires multiple callbacks per call (Dial action, per-number status, retries on non-2xx). The bridge writes a marker file per `CallSid` so tags, notes, and workflows execute exactly once.
- **`answerOnBridge="true"`** — GHL/Twilio see the call as "ringing" until a CSR actually picks up. Customers hear 3CX hold music, not Twilio silence; CRM status reflects reality.
- **`callerId` pass-through** — CSR sees the actual customer number, not the Twilio DID.
- **Original-caller preservation** — when bridging to 3CX, the customer's number is forwarded so 3CX agents and call records show the real caller.
- **E.164 normalisation** — phone numbers are normalised before hitting GHL, preventing duplicate contacts.
- **Append-only structured logging** — every step is timestamped and grep-able.

---

## 5. Scope of Work — What Is Included

### 5.1 Build & Code (delivered)

- [x] PHP bridge with Twilio inbound + status webhook handlers
- [x] GHL REST client (contact upsert, tags, notes, workflows)
- [x] HMAC-SHA1 Twilio signature validation
- [x] CallSid-based idempotency
- [x] Structured logging
- [x] Composer / autoload setup
- [x] nginx config template

### 5.2 Test Harness (delivered)

- [x] `verify_connections.php` — pre-flight connectivity checker (Twilio API, 3CX SIP trunk health, GHL API auth)
- [x] `test_ghl_webhooks.php` — exercises both GHL endpoints without making a real call
- [x] `simulate_twilio_call.php` — simulates a Twilio inbound webhook end-to-end
- [x] `simulate_3cx.php` — simulates 3CX call events (full / missed / concurrent)

### 5.3 Provisioning & Configuration (one-time, included)

- Twilio: number provisioning, Elastic SIP Trunk setup, Termination + Origination URIs, Voice & Status webhook URLs, signing.
- 3CX: SIP trunk to Twilio (registered), inbound rule mapping DID → queue 800, queue creation (round-robin, "keep in queue", hold music, max wait), CSR extension creation.
- GHL: API key + Location ID issuance, "Forward Call" set to the Twilio DID, voicemail disabled on the GHL number, missed-call workflow created and wired.
- Bridge server: deployment to client-supplied VPS, nginx + TLS (Let's Encrypt) configuration, file permissions, log rotation.

### 5.4 Documentation (delivered)

- `README.md` — full step-by-step setup, troubleshooting matrix, monitoring commands.
- This proposal.
- Inline code documentation in every webhook handler.

### 5.5 Acceptance Tests (run with the client at handover)

1. **Connectivity** — `php test/verify_connections.php` shows all green.
2. **GHL endpoints** — `php test/test_ghl_webhooks.php` returns HTTP 200 and creates a test contact + call log in GHL.
3. **Single live call** — calling the GHL support number rings the CSR's 3CX app; after hangup, the contact and call appear in GHL with status, duration, and (if enabled) recording URL.
4. **Missed call** — call placed with no CSR available → call sits in queue → times out → contact tagged `Missed Call` → missed-call SMS dispatched by GHL workflow.
5. **Concurrent calls** — 3 simultaneous calls from 3 different numbers → all 3 enter queue, none rejected → CSRs receive in round-robin → all 3 contacts in GHL.

---

## 6. Out of Scope (clearly stated)

To keep expectations crisp, the following are **not** part of this engagement unless added as a change order:

- Outbound calling from GHL through 3CX (the current bridge is inbound-only).
- 3CX licensing, hosting, or PBX administration beyond the queue and trunk needed for this flow.
- Twilio account funding, monthly minutes, or carrier costs (passed through to the client at Twilio's published rates).
- GoHighLevel subscription, agency plan, or sub-account changes.
- IVR menus, call whisper, voicemail-to-email, after-hours routing logic (can be added — see §10).
- Custom GHL workflows beyond the single missed-call SMS used as the reference implementation.
- CRM data migration or contact deduplication of historical records.

---

## 7. Requirements From the Client

To execute, we need:

| # | Item | Format |
|---|---|---|
| 1 | GoHighLevel sub-account access | Admin user + Location ID |
| 2 | GHL API key with `contacts.write`, `tags.write`, `workflows.execute` scopes | Bearer token |
| 3 | Twilio account access | Account SID + Auth Token, or invited user |
| 4 | Twilio number(s) for the support line | E.164, owned by the client |
| 5 | 3CX admin portal credentials | Admin user/pass + 3CX FQDN |
| 6 | Linux VPS (or shared host with PHP 8.1+) for the bridge | Public HTTPS, sudo for nginx setup |
| 7 | A subdomain pointing to the VPS | e.g. `bridge.yourcompany.com` |
| 8 | List of CSR users for 3CX extensions | Name + email |
| 9 | Final missed-call SMS copy | One short sentence |

We will not start chargeable provisioning work until items 1–7 are in hand.

---

## 8. Timeline

Assuming all access from §7 is provided on day 1, the engagement runs **5 working days**:

| Day | Milestone |
|---|---|
| **Day 1** | Twilio number provisioning, SIP trunk creation, GHL API access verified, VPS bootstrap (nginx + TLS + PHP). |
| **Day 2** | 3CX SIP trunk to Twilio, queue 800 + CSR extensions, inbound rule mapping. |
| **Day 3** | Bridge code deployed, `.env` populated, `verify_connections.php` green, GHL webhook test green. |
| **Day 4** | End-to-end live call, missed-call test, concurrent-call test, log audit. |
| **Day 5** | Client acceptance, CSR walkthrough/training, handover document, 30-day support window begins. |

Slippage is most commonly caused by GHL forwarding misconfiguration or 3CX trunk registration delays — both items we de-risk by validating credentials before code deployment.

---

## 9. Pricing

> Replace the placeholder figures with your agreed rate before sending.

| Line item | Amount |
|---|---|
| One-time build, configuration, and acceptance testing | $X,XXX |
| Telephony provisioning labour (Twilio + 3CX + GHL) | included |
| Documentation + handover | included |
| 30-day post-launch support (bug fixes, no new features) | included |
| **Total fixed fee** | **$X,XXX** |

**Client-paid pass-through costs** (not invoiced by us):

| Pass-through | Typical |
|---|---|
| Twilio DID monthly | ~$1.15 / number / month |
| Twilio inbound minutes | ~$0.0085 / min |
| Twilio Elastic SIP Trunk inbound | per Twilio's pricing |
| GHL subscription | per existing plan |
| 3CX licensing | per existing plan |
| VPS hosting | ~$5–$10 / month for the bridge |

### 9.1 Optional Ongoing Support

| Plan | Price | Includes |
|---|---|---|
| Reactive | $XXX / hr | Break-fix, no SLA |
| Retainer (recommended) | $XXX / mo | Up to N hrs/mo, 1-business-day response, log monitoring, quarterly review |

---

## 10. Roadmap — Future Phases

Items the client may want, all build cleanly on top of the existing bridge:

- **Outbound click-to-call from GHL** — click a number in GHL, ring the CSR's 3CX extension first, then dial out via Twilio.
- **IVR / time-of-day routing** — "Press 1 for Sales, 2 for Support" and after-hours fallback.
- **Call recording into GHL** — already wired into the call note; can be extended to push the audio file into the GHL contact's media.
- **Real-time CSR dashboards** — surface live queue depth, longest hold, agent state from 3CX into a small internal panel.
- **CRM-driven priority routing** — VIP-tagged contacts skip the queue or ring a dedicated extension.
- **SMS auto-reply on missed call** — implemented as the reference workflow; can be extended to multi-step nurture sequences.

---

## 11. Risks & Mitigations

| Risk | Likelihood | Mitigation |
|---|---|---|
| GHL changes its forwarding behaviour or API surface | Low | Bridge uses GHL's stable v2 REST API + Twilio-compatible webhook endpoints. Single-file fix area if GHL changes. |
| Twilio outage | Low | Twilio publishes 99.95%+ historical uptime; for higher resilience, add Telnyx as a secondary carrier (bridge already supports it — see `telnyx/`). |
| 3CX SIP trunk de-registers | Medium | Trunk auth credentials and IP ACL, plus 3CX's own retry logic. Monitoring command included in `README.md`. |
| Webhook URL leaks | Low | All inbound endpoints require valid Twilio HMAC-SHA1 signature; unsigned requests are rejected (or logged in dev mode). |
| Duplicate CRM writes | Eliminated | Idempotency markers per `CallSid`; one CRM mutation per call, guaranteed. |

---

## 12. Why This Architecture (vs. Alternatives)

| Alternative | Why we didn't choose it |
|---|---|
| Zapier / Make.com between Twilio and GHL | Adds 5–30s latency, monthly cost scales with call volume, no signature validation, no idempotency primitive. |
| Direct GHL native phone with no 3CX | Loses every 3CX call-handling feature your CSRs already use. |
| 3CX CRM plugin only | One-way: 3CX writes to GHL but GHL workflows don't see ringing/missed states in real time. |
| Custom Node/Go service | Heavier ops footprint for a flow that's a few hundred lines of stateless PHP. PHP runs on any LAMP host. |

The chosen architecture is **stateless**, **carrier-portable** (Twilio today, Telnyx tomorrow — both shipped), **observable** (single append-only log), and **boring in the right places** (no databases, no queues, no background workers — just two webhook handlers and a REST client).

---

## 13. Acceptance & Sign-off

This proposal becomes an engagement when the client signs below or replies in writing accepting the scope, timeline, and fee in §5–§9.

| Party | Name | Signature | Date |
|---|---|---|---|
| Client | | | |
| Hexatech Solution | | | |

---

**Questions or changes:** dev@hexatechsolution.com
