Wiring CallRail → GoHighLevel → Clio with real routing logic (not just webhooks)
Most law-firm intake stacks I've seen are duct-taped together: a CallRail tracking number rings somewhere, a GHL contact gets created if you remember to wire the webhook, and a paralegal eventually re-types the matter into Clio the next morning. The result is leak: caller language, source attribution, and qualification context get lost between systems. This post walks through the production pipeline running at Vasquez Law Firm — 4 offices, mixed practice areas, bilingual caller base — and the routing logic that makes it actually work end-to-end. Not vendor-marketing-page logic. The rules we tuned in production after live calls failed in ways we hadn't anticipated.
The high-level pipeline
Inbound calls hit CallRail tracking numbers (one per office, one per major practice-area campaign). CallRail forwards the call to a SIP endpoint that lands on our LiveKit-based voice agent. As soon as the agent has captured language preference and intent, a webhook fires into GoHighLevel — creating or matching a contact, opening an opportunity in the right pipeline. The agent continues qualification (matter type, urgency, conflicts), and on completion, an n8n workflow translates the GHL opportunity into a Clio matter with the correct sub-type, custom fields, and source attribution. The entire loop runs without a human touching it for typical intakes. Humans get involved at consult or earlier when the agent escalates.
Why Zapier wasn't enough
Our first version used Zapier as the glue. It worked for the happy path — call comes in, contact gets created, matter eventually appears in Clio. It broke in three ways that ended up being load-bearing: (1) Zapier's per-zap pricing scaled badly when we added language-specific routing, practice-area sub-flows, and conflict checks — every conditional was its own zap; (2) error handling was opaque — when a zap failed at 11pm, we found out the next morning that 6 calls were missing matters; (3) state sharing across steps was awkward — passing the language preference + practice area + conflict-check result through a chain of zaps got expensive in API calls and brittle in retries.
We migrated to n8n self-hosted in 6 weeks. The win was less about cost (though it is cheaper at our volume) and more about debuggability. Every workflow run is inspectable, retriable, and version-controlled. When a Tuesday-morning call ends up routed wrong, we can replay the workflow with the exact inputs and see where the logic chose the wrong branch.
Routing rules that actually matter
The first thing the routing layer cares about is language. If the caller spoke Spanish on the agent call, every downstream step uses Spanish: the GHL pipeline tag, the Clio matter notes language, the consult-confirmation SMS. This sounds obvious; it is not how most off-the-shelf vendors handle it. Most treat language as a routing branch (English queue vs Spanish queue) rather than as a property of the matter itself. We needed it to be a property — because the same firm handles the matter and the bilingual caller will continue to interact in their preferred language across emails, follow-ups, and consult.
Second is practice area. Each office has a different mix. Charlotte is heavy immigration and PI; Smithfield is more PI and family law; Orlando is more PI and immigration. Routing by office without considering practice area means an immigration call to Smithfield ends up with the wrong on-call attorney. The agent captures matter type explicitly during intake, and the n8n workflow uses that — not the office of the tracking number — to assign the matter.
Third is urgency. A criminal defense intake mentioning an arrest within 24 hours, an immigration call mentioning a Notice to Appear, or a family law call disclosing domestic violence triggers an immediate route to the on-call attorney's mobile, not a queue. The agent doesn't decide this on its own from raw natural-language understanding; we have explicit pattern rules — the agent flags certain entities (NTA, master calendar, in-custody, DV) and the routing layer reacts. Hard-coding this beat trying to infer urgency from sentiment.
- ✓Caller language preference is a property of the matter, not a routing branch
- ✓Practice area drives routing, not the office that received the tracking number
- ✓Urgency is detected via explicit entity flags, not sentiment inference
- ✓Conflict checks run BEFORE the consult is booked, not after
- ✓Source attribution (campaign, tracking number, URL) flows into Clio matter custom fields
Conflict checking before consult booking
This was the change that mattered most for ethics-rule confidence. The original flow booked the consult first and ran the conflict check overnight as a separate process. That meant on rare occasions we would have to call a caller back to cancel — a bad experience and a small but non-zero risk. Now the agent collects both party names (where applicable — divorce, family law, multi-defendant criminal) and a Clio search runs synchronously against existing client and matter records before the agent offers a consult slot. If a conflict is found, the agent collects contact info but offers a written follow-up rather than a consult, and the matter is flagged for human review.
Source attribution that survives
CallRail's value over a generic phone provider is the ability to attribute a call to a specific campaign, ad, or landing page. We used to lose that attribution by the time the matter was in Clio — it would be sitting in a CallRail report nobody opened. Now CallRail's session data (campaign, source, medium, landing page, search keyword if available) flows through GHL into the Clio matter as custom fields. When we review wins by source quarterly, the data is in the same system as the matter outcomes. ROI math becomes possible.
What broke during rollout
Three things broke that I want to flag for anyone going down this path. First: race conditions on contact creation. If two calls came in for the same caller within seconds (which happens — caller hangs up and immediately redials), GHL would create two contacts and we'd end up with split matters. Solution: idempotency key on contact creation, derived from phone number + practice area + 5-minute window.
Second: Clio API rate limits. We hit them once during a marketing-driven traffic spike and matters started failing to create. The n8n retry logic now uses exponential backoff with jitter, and we have a fallback queue for matters that can't be created in real time — they get created within 15 minutes when the API has capacity. We've never lost a matter to this since.
Third: timezone confusion in scheduling. The agent runs in UTC; offices are in Eastern; some attorneys travel and want consults in their local time. We standardized on storing every timestamp in UTC + storing the office and attorney's home timezone separately, and rendering in the appropriate zone at the consumer (consult confirmation email, calendar invite, etc). Worth doing on day one if you're building this.





