Linking Conversations to Leads
By default, an inbound message creates a thread keyed by the raw provider id (e.g. whatsapp:447700900000). The thread is anonymous in the sense that it has no application context. Linking ties it to a parent or a lead so the message history shows alongside the application record.
Three ways a thread gets linked
- Automatic by phone match — WhatsApp inbound from a phone we already have on a parent's profile (E.164-normalised). The new message lands on the existing parent's thread.
- Automatic by Page-Scoped ID — Messenger inbound from a Page-Scoped ID (PSID) we've seen before for that same Page. Threaded by PSID.
- Manual link — you click Link to lead in the thread header, search the roster, pick a parent or applicant.
Once linked, the conversation key flips from whatsapp:447700900000 to whatsapp:42 (where 42 = the parent's id). All future inbound from that phone, on that channel, joins the same thread.
Linking creates parents
Manually linking to a lead that has no parents rows yet will:
- Create a
parentsrow from the lead's parent fields (lead.parent_name,parent_email,parent_phone). - Link the new parent into the lead's family via
family_members. - Bind the conversation to that parent.
This is the same dedupe logic forms use, so a parent who fills out a form and later WhatsApps you ends up as a single record either way.
Unlinking
The same header has an Unlink option (for admins). Unlinking reverts the conversation key to the raw provider id. The history of past messages doesn't move — only future inbound starts a new thread.
The parent isn't deleted; just the conversation binding is removed.
"Create enquiry from this conversation"
For brand-new contacts who came in cold via WhatsApp, the thread header offers a shortcut: Create enquiry. This:
- Creates a new enquiry (lead with
roster_type = enquiry) using the parent's name + phone. - Auto-links the conversation to the new lead.
- Drops you on the new lead's page with the conversation visible in the side rail.
The fields auto-fill from whatever the conversation already knows (sender name, profile picture, phone). You can edit them on the new lead's page.
How does the inbox find the right thread?
For each incoming webhook, the channel handler:
- Normalises the sender identity (E.164 for phone-based; openid for WeChat; psid for Messenger; igid for Instagram).
- Calls
ParentService::findOrCreate(<email-or-phone>)— match-or-create. - Builds a
ConversationKey('<channel>', <parent_id-or-raw>). - Inserts the inbound row.
- Bumps the unread count + fires push notifications.
All five steps happen inside the webhook handler — there's no separate background reconciliation job to wait on.
Multiple channels for the same parent
Once a parent is linked across channels, the inbox shows all their threads as siblings under the same parent header. Each channel keeps its own thread (replies fan out per-channel, as enforced by platform rules) — but the application context (linked lead, family, notes) is shared.