There's a version of connecting SMS to GoHighLevel that works right up until the moment a contact replies. You wire an outbound webhook: GHL fires a workflow, a Zapier zap or a custom endpoint catches it, forwards the text to a sending API, and you feel clever. Then someone texts back "yeah, 2pm works," and it vanishes. Or it lands as a brand-new contact with no history. Or it shows up in a channel your VA never checks. The send worked. The conversation didn't.
Full disclosure: I work for Ready, and we built a native GHL integration precisely because we watched agency after agency try to duct-tape this with webhooks and lose the half of texting that actually matters — the inbound half. So I'm biased. But the mechanics below are true regardless of whose platform you use, and if you understand them you'll make a better call either way.
The webhook works one direction. That's the whole problem.
A webhook is a fire-and-forget event. GHL says "SMS should go out to this number, here's the body," and your middleware sends it. Clean, simple, one direction.
Inbound is a different animal. When a contact replies, that reply arrives at your sending provider's number, not at GHL. For it to become part of a conversation inside GHL, something has to:
- Receive the inbound message from the carrier.
- Match it to the right contact — by phone number, which sounds trivial and isn't.
- Post it back into GHL's conversation thread for that contact.
- Do all of that per sub-account, so client A's replies don't leak into client B's inbox.
A webhook gives you step 1. Steps 2 through 4 are where DIY plumbing quietly falls apart, and the failures are the kind you don't notice until a client asks why they missed a hot lead's reply three days ago.
Reply matching: phone numbers aren't as unique as you think
The naive match is "look up the contact whose phone equals the inbound number." It breaks on the boring reality of real CRM data:
- The same person exists twice in the sub-account because they filled out two forms.
- The number is stored as
+15551234567in one place and(555) 123-4567in another, and your zap's exact-match lookup misses. - The number belongs to a household — spouse replies from the number on file for a different contact.
- The lead was reassigned and the phone field got overwritten.
With a webhook chain, you own the matching logic, which means you own the edge cases. Most DIY setups do a simple exact-string lookup, and when it misses, the reply either errors out or creates an orphan contact. Now your review flow, your appointment confirmation logic, your "did they reply YES" branch — all of it keys off a contact record that never got the reply.
A native OAuth integration matches on the carrier-normalized number and threads the reply onto the existing GHL conversation, so a "yeah" lands where the "C or 1 to confirm" went out. That threading is exactly what keeps a confirmation flow from booking a real reply as no-response — a failure mode I've written about on the healthcare side, and it applies to any two-way flow.
Opt-out sync: STOP has to propagate, not just log
Here's the one that gets agencies in actual legal trouble. When a contact texts STOP, three things need to happen:
- The carrier suppresses future traffic on that number (this happens at the network level regardless of your setup).
- Your sending platform records the opt-out so you don't try again.
- GHL knows the contact is now DND, so no workflow re-enrolls them and re-fires an SMS step.
A webhook that only handles outbound has no idea a STOP came in. The opt-out lives at your provider, GHL never hears about it, and the next campaign or nurture workflow happily tries to text a number that's already opted out. Best case, it's carrier-filtered and you eat the segment. Worse case, your logic sends over a different route and you've now texted someone who said stop — which is the fact pattern in a TCPA complaint.
Ready's opt-out handling honors inbound STOP/UNSUBSCRIBE and propagates it so the contact can't be messaged again across campaigns, and because the integration is two-way, that state syncs back into the connected GHL location. The broader problem — opt-out that doesn't cross channels or platforms — is worth understanding on its own; I dug into it here.
Contact dedup: the orphan-record tax
Every time inbound matching misses, you don't just lose the reply — you often create a duplicate. The zap can't find the contact, so it makes one. Now the sub-account has two records for the same person: the original with all the history, and the ghost with a single inbound message.
Multiply that across a busy list and you get:
- Attribution that's split across two records, so reporting undercounts engagement.
- Automations firing off the record that doesn't have the tags/pipeline stage they check for.
- A VA answering the ghost record's message while the real conversation sits on the original.
Native sync sidesteps most of this because the inbound message is threaded onto the matched contact inside GHL rather than posted by an external process that has to guess. Fewer guesses, fewer orphans.
Inbox unification: where does the reply actually live?
With DIY plumbing you frequently end up with two inboxes. Replies land at your sending provider's dashboard and — if your zap works — inside GHL. Now your team has to watch both, or you have to build a second sync to keep them in agreement. Every additional hop is another place a message can silently not arrive.
Native integration collapses this: inbound replies land in the platform's conversations inbox and in GHL for connected accounts, in the same thread, so there's one place to answer from. For an agency running many clients, one unified thread per contact per sub-account is the difference between "we caught the lead" and "the lead booked with the competitor."
The tradeoff, laid out honestly
Webhooks aren't wrong for everything. If you are only sending one-way transactional blasts and no one is expected to reply — a shipping notification, a one-off appointment reminder with no confirm step — a webhook is genuinely fine, and cheaper to reason about. The break happens the moment you want a conversation.
| Capability | DIY webhook / Zapier | Native GHL integration |
|---|---|---|
| Outbound send | ✅ Works | ✅ Works |
| Inbound reply threading | ⚠️ You build & own the matching | ✅ Threaded onto matched contact |
| Number normalization | ⚠️ Your logic; exact-match misses | ✅ Carrier-normalized matching |
| STOP → GHL DND sync | ❌ Usually not; outbound-only | ✅ Opt-out propagates back |
| Contact dedup | ⚠️ Orphan records on miss | ✅ Fewer orphans by design |
| One inbox | ❌ Provider + GHL, watch both | ✅ Unified, in GHL for connected accounts |
| Per-sub-account isolation | ⚠️ You partition it manually | ✅ Mapped per location |
| Setup effort | High (and you maintain it) | OAuth connect |
Two things worth flagging for agencies specifically. First, native mapping keeps sub-accounts isolated — client A's inbound can't bleed into client B — which is the whole point of running clients in separate locations. Second, isolation at the messaging layer doesn't decide your 10DLC registration strategy; that's a separate call about whether each sub-account needs its own brand and campaign, which I covered in 50 sub-accounts, 50 registrations.
What native actually costs you
Not much in effort — it's an OAuth connect, mapped per location. What it costs in money is the same per-segment pricing you'd pay anyway: $0.02/segment on Standard plus the $0.0045 carrier pass-through, with the drop to $0.016 applying automatically once a location clears 50,000 segments in a month. You're not paying a premium for the sync; you're avoiding the hidden cost of maintaining brittle middleware and eating the segments you waste texting orphan records and opted-out numbers.
If you want to check the setup mechanics before committing, the GHL SMS setup guide walks the connect step by step.
The practical takeaway
If your texting is one-directional and always will be, a webhook is a reasonable, cheap choice — don't let anyone talk you out of it. The instant you expect a reply, a confirmation, a STOP, or a conversation your team has to answer, the webhook route starts leaking exactly the things that make SMS worth doing: the matched reply, the honored opt-out, the single clean thread.
Native sync isn't magic. It's just handling steps 2 through 4 so you don't have to build and babysit them. If you're on GoHighLevel and tired of watching replies fall into a gap, you can connect a location and send your first 2,500 credits free at Ready, or sign up here and wire it up in an afternoon. Either way — decide based on whether people reply, not on which setup looked clever first.