Agencies that serve franchises and multi-location businesses eventually get this support ticket: "A customer replied to our text and it showed up in the wrong location's inbox." The contact is real. The reply is real. It's just sitting in a sub-account that nobody on the right team checks. Someone forwards a screenshot, someone else copies the message into another thread, and the customer waits a day for an answer to a two-word question.
Almost every one of these tickets comes from the same setup. One person exists as a contact in two or more GoHighLevel sub-accounts. A franchise owner might run three of your client's locations. A patient might visit two clinics in the same group. A buyer might deal with two branches of the same supplier. This guide explains how Ready's GHL sync routes inbound replies, why the isolation you're seeing is deliberate, and how to audit and configure numbers and opt-outs so replies reach the team that should answer them.
How Ready's GHL sync is mapped
Ready connects to GoHighLevel through OAuth. The two-way sync of inbound and outbound messages is mapped per GHL location, which Ready treats as a sub-account. Each sub-account has its own numbers and its own conversation history, and it either registers its own business or shares your approved registration.
This is the property you want. If sub-account A's messages could bleed into sub-account B, a dental office could read another practice's patient threads, and a franchisee could see a competing franchisee's leads. Agencies choose per-location mapping precisely because one client's data never touches another's.
The tradeoff is that GHL doesn't know two contacts are the same human, and the sync doesn't either. Contacts in GHL live inside a location. The same phone number in three locations is three separate contact records, each with its own thread. For a background on why native sync keeps these threads intact where webhook setups lose them, see what native sync actually preserves.
The routing key is the number they texted
When a reply comes in, it's tied to the sending number it was sent to. That number belongs to exactly one sub-account, so the reply lands in that sub-account's thread for that contact.
The contact's intent doesn't affect routing. Neither does which location they visited last or which team "owns" them in your head. If the franchise owner replies to a text from Location B's number about something Location A sent yesterday, the reply goes to Location B. The system is working correctly. Your number assignment is just ambiguous.
Why replies end up in the "wrong" sub-account
Here are the patterns that produce most misrouted-reply tickets:
| Symptom | Likely cause | Fix |
|---|---|---|
| Reply lands in a sub-account that didn't send the latest message | Contact replied to an older thread on their phone (both businesses look like "texts from the brand") | Distinct sender identity per sub-account; sign every message with the location name |
| Two sub-accounts share a number someone added by hand | Number reassigned or ported between sub-accounts without cleanup | One number, one sub-account; audit with GET /v1/numbers per sub-account |
| Contact gets the same promo twice from two locations | Same contact on two sub-accounts' campaign lists | Agree on which sub-account owns brand-wide campaigns |
| STOP in one sub-account, still texted by another | A STOP blocks the number on the parent account and every sub-account; mark GHL DND in the other locations too | Decide a brand-level opt-out policy (below) |
| Rotation sends a contact from a new number mid-conversation | Assumed rotation reshuffles existing contacts | It doesn't: a contact keeps the number it already texts with |
The first row causes the most trouble. On the customer's iPhone, two franchise locations texting from two local numbers look like two near-identical threads. People reply to whichever one is on top.
Auditing which number a contact last talked to
Before changing any configuration, find out what actually happened. Work through these steps in order.
Step 1: Find every sub-account the number exists in
In GHL, search the phone number at the agency level, or in each relevant location if you're working client by client. List every location where the contact exists. For a franchise client this is usually two to five.
Step 2: Read the thread in each one
Open the conversation in each sub-account and note three things:
- The Ready number used on the last outbound message
- The timestamp of the last outbound
- The timestamp and content of the last inbound
The sub-account with the most recent inbound is where the contact actually replied. If that's not the sub-account whose team needs to act, you've confirmed a routing-by-number issue, not a sync failure.
Step 3: Confirm from the API side
If you'd rather check outside GHL, or you're auditing many contacts, the messaging API gives you the same history. Using the API key for the account you're auditing:
GET https://api.tryready.com/v1/messages
X-API-Key: <key> Page backward through results with before_id. To see which numbers that account holds:
GET https://api.tryready.com/v1/numbers
X-API-Key: <key> To check whether the contact has opted out there:
GET https://api.tryready.com/v1/opt-outs/+12125550199
X-API-Key: <key> The messages API docs cover the response shape and filtering. If you're scripting an audit across dozens of sub-accounts, start with the quickstart and list sub-accounts with GET /v1/sub-accounts.
Step 4: Write down the answer
For each duplicated contact, record which sub-account and which number they actually talk to. This list drives the number-assignment decisions below. It also tells you whether you have a handful of edge cases or a structural problem.
Setting up number assignment so replies reach the right team
One number, one sub-account, always
Each sub-account should own its numbers outright. If a number was moved between GHL locations during an onboarding or a franchise sale, confirm it no longer appears in the old sub-account. A number that's half-moved is the most common source of genuinely confusing routing.
Make each sub-account recognizable in the message itself
The cheapest fix is copy, not configuration. Open or sign every text with the location: "Hi Dana, it's Maple Ave Dental." When the customer scrolls their messages, they can tell which thread is which and reply to the right one. This costs a few characters. Keep the total under 160 GSM-7 characters so it stays one segment.
Local presence and rotation stay inside the sub-account
If a sub-account has several numbers, Local presence sends from the one closest to the contact's area code. In GHL, the #from:local# tag picks it. #from:rotate# spreads new contacts across numbers inside the local area-code group.
Two details matter for this problem:
- A contact keeps the number it already texts with. Rotation won't move an existing conversation to a new number, so turning it on doesn't scatter a contact's replies.
- Selection only happens among that sub-account's own registered numbers. Local presence never reaches into another sub-account, and it doesn't bypass 10DLC registration. Every number still sits on the account's registered campaign.
Local presence has a listed price of $0.0015 for each segment of a delivered text. Teams turn it on because a local area code tends to look more familiar to the recipient. Treat any reply lift as something to measure on your own traffic, not a promise.
Decide who owns brand-wide campaigns
If corporate and each franchisee all send the same seasonal promo, a franchise owner on three lists gets it three times. They then reply to whichever thread is on top. Pick one sub-account to own brand-wide campaigns, and have the locations send only location-specific messages.
The cost difference is small per contact, but it adds up. Take an example with stated assumptions: a 150-character promo with no emoji (1 segment), and 400 contacts who each appear in three sub-accounts.
- Sent from all three sub-accounts: 400 × 3 = 1,200 billed segments, each at ($0.0075 + $0.0045).
- Sent from one owning sub-account: 400 billed segments, each at ($0.0075 + $0.0045).
Ready's all-in prices per segment include the carrier fee but not screening: Scrub lists and Number validation, the two switches in Settings under Compliance, each add $0.003 per segment when switched on, shown as screening on your bill, and both are off by default. Unless you turn the opt-out footer off, the first text to each contact, and one again from time to time, also gets "Reply STOP to opt out" and your business name if the message does not already have them, so a text close to the segment limit can bill one more segment.
Replies are billed like a sent text at the same per-segment rate plus carrier fee. Duplicate sends also produce duplicate replies, each one billed and each one landing in a different inbox. If you rebill clients, this shows up as a reconciliation gap. For more on that, see the segment-count audit.
Opt-outs when the same person is in several sub-accounts
Ready honors inbound STOP and UNSUBSCRIBE automatically. A STOP to any sub-account blocks that number on your agency account and on every one of its sub-accounts. GHL's own per-location DND does not copy across locations, so mark the contact DND in the other locations too. Verify any number with GET /v1/opt-outs/:phone, which gives the send gate's own answer.
This is good practice and risk reduction, not a legal opinion. Compliance remains the sender's responsibility. If you need clarity on who carries that responsibility between you and your client, see the agency to client liability split.
When to consolidate instead
If the audit in step 4 shows dozens of duplicated contacts per client, separate sub-accounts may be the wrong shape for that client. A franchise whose customers routinely move between locations sometimes works better with one sub-account and location tags. Others genuinely need the isolation, because franchisees are separate businesses with separate data. Both are valid. The tradeoffs, including registration, are covered in when to consolidate sub-account senders.
This isn't GHL-specific, either. Platforms using Ready's API sub-accounts directly hit the same question whenever one end customer belongs to two of their tenants. The answer is the same: the number is the routing key, so make number ownership unambiguous.
The practical takeaway
A reply in the "wrong" sub-account usually means the sync is doing its job: keeping each GHL location isolated and routing every reply by the number it was sent to. To fix it:
- Audit where each duplicated contact actually replies.
- Give every number exactly one owner.
- Make each sub-account identifiable in the message copy.
- Pick one owner for brand-wide campaigns.
- Write down a brand-level opt-out policy.
If you're setting this up fresh, you can connect GHL and send your first 20 one-to-one texts free at tryready.com/signup. Run the audit on a test contact before you point a franchise client at it.
Frequently asked questions
Can the same phone number be used in two GoHighLevel sub-accounts?
It shouldn't be. With Ready's GHL sync, each sub-account owns its numbers outright, and an inbound reply is routed by the number it was sent to, so that number has to belong to exactly one sub-account. If a number was moved between locations during onboarding or a franchise sale, confirm it no longer appears in the old sub-account. A half-moved number is the most common source of genuinely confusing routing, and you can audit it with GET /v1/numbers per sub-account.
If a customer texts STOP to one GHL location, are they opted out everywhere?
Yes, on Ready. A STOP to any sub-account blocks that number on your agency account and on every one of its sub-accounts. GHL's per-location DND settings do not copy across locations, so mark the contact DND in the other locations too. You can check any number with GET /v1/opt-outs/:phone.
Does number rotation in GoHighLevel change the number an existing contact texts with?
No. A contact keeps the number it already texts with, so turning on #from:rotate# won't move an existing conversation or scatter its replies. Rotation only spreads new contacts across numbers inside the local area-code group, and Local presence only picks among that sub-account's own registered numbers. It never reaches into another sub-account and doesn't bypass 10DLC registration. Local presence has a listed price of $0.0015 for each segment of a delivered text.
Why does the same contact show up as separate records in different GHL locations?
Because GHL contacts live inside a location, and neither GHL nor Ready's sync knows two records are the same person. The same phone number in three locations is three separate contacts, each with its own thread. That isolation is deliberate: it keeps one client's patient threads or leads from ever touching another's. The fix isn't merging. It's making number ownership clear and signing every message with the location name so customers reply to the right thread.