This post is for the person who has to wire Click-to-WhatsApp ads into a hospital or clinic CRM: a marketing-operations lead, a CRM developer, or an agency analyst. It assumes you already run the ads and answer the chats, and that the reports show "conversations started" but not which ad produced a booked appointment. If you still need the set-up basics and the cost model, read our post on Click-to-WhatsApp ads for clinics first. This one stays in the plumbing.
The aim is a closed loop in four steps. Capture the identifier Meta sends when a chat starts from an ad. Store it against the right patient record. Update it as the lead moves through your pipeline. Send the milestones back to Meta so the ad system can optimise for qualified leads and not only for chats. Each step has failure modes, and most "attribution tools" hide them.
Field names and request shapes below come from Meta's documentation as it stood in October 2026. Meta changes API versions often, so verify against the linked pages and your own raw payloads before you build.
Step 1: What arrives in the webhook
When a person taps a Click-to-WhatsApp ad and sends the first message, the WhatsApp Business Platform delivers an incoming-message webhook. Where the chat came from an ad, the message object carries a referral object. Meta's messages webhook reference lists these fields:
| Field | Meaning |
|---|---|
ctwa_clid | The Click-to-WhatsApp click ID. Omitted for messages from an ad in WhatsApp Status |
source_id | ID of the ad that produced the message |
source_type | Shown as ad for ad-originated messages in Meta's example |
source_url | URL associated with the ad |
headline | The ad headline |
body | The ad's primary text |
media_type | image or video |
welcome_message | Object whose text holds the ad's greeting |
In a parsed webhook the path is entry[].changes[].value.messages[].referral, next to the sender's from number, the message id and the timestamp. Log the raw JSON of the first few hundred inbound messages before you write a parser. Integrators have reported referral data being dropped by wrappers, which is easy to miss because nothing errors.
Two points save debugging time. The referral object appears on the message that opens the thread from the ad, not on every later message. And because the ctwa_clid is absent for Status ads, a missing click ID does not by itself mean the lead was organic.
Step 2: Design the storage
Do not put ctwa_clid in a single column on the contact. A patient can click three different ads over two months, ask about different services, and book on the third. Use a separate touch table.
| Table | Key fields |
|---|---|
contact | internal id, WhatsApp number (normalised), consent status, first seen, source summary |
conversation | id, contact id, started at, entry point (ad, organic, QR, website button), service line, location |
ad_referral | conversation id, ctwa_clid, source_id, source_type, headline, body, media_type, received_at, raw JSON |
stage_event | conversation id, stage, occurred at, actor, value, CAPI sent flag, CAPI sent at, response code |
Rules worth enforcing in code:
- One referral per conversation start. If a returning patient messages again without an ad, create a conversation with entry point organic, and do not overwrite the earlier referral.
- Store the raw JSON. Field names or nesting will change, and you will want to re-parse historic data.
- Normalise the number before matching. WhatsApp sends numbers without a plus sign. Match against your appointment system on a canonical format, or you will split one patient into two records.
- Treat a missing referral as unknown. Do not label it organic. Reasons for absence include Status ads, a patient who messages the number saved from a previous click, a QR code, and a provider that strips the object.
If your chat sits behind a business solution provider, ask them in writing whether the referral object is passed through unchanged and whether they expose the click ID. Tools built for this purpose, such as WaCRS, pass it through to the CRM record, but test whichever one you use against your own phone.
Step 3: Map pipeline stages to events
The call centre or front desk moves a conversation through stages. Each stage that a human has verified is a candidate event to send back. A reasonable hospital mapping:
| CRM stage | Meaning | Event to send |
|---|---|---|
| Conversation started | Inbound message from ad | None; Meta already reports it |
| Qualified | Right service, serviceable city, reachable patient, not a spam or vendor message | QualifiedLead |
| Appointment confirmed | Slot booked and acknowledged by the patient | LeadSubmitted (or a custom event, see below) |
| Consultation fee paid | Payment received at booking or at the visit desk | Purchase with the fee as value |
Meta's business messaging Conversions API documentation lists the supported event names, including LeadSubmitted, QualifiedLead, Purchase, RatingProvided and ReviewProvided. There is no appointment-specific event in that list, so decide on one convention, write it down, and keep the mapping in configuration, not in code. Check Events Manager after your first batch to confirm that the events were accepted.
Two cautions on what you send. First, send business outcomes and nothing clinical. Never put a diagnosis, procedure name, test result or symptom into an event parameter. Meta's Business Tools Terms restrict sharing sensitive information such as health data, and a hospital's service line can be sensitive by itself, so keep custom parameters generic. Second, the value on a Purchase should be the fee actually paid in the thread, not a projected procedure value.
Step 4: The Conversions API request
For WhatsApp, the documentation specifies the following for each event. The request goes to your dataset's events endpoint with these elements:
event_name: one of the supported names above.event_time: when the stage change actually happened, in Unix seconds.action_source:business_messaging.messaging_channel:whatsapp.user_data.whatsapp_business_account_id: your WhatsApp Business Account ID.user_data.ctwa_clid: the stored click ID, unmodified.custom_data: currency and value where relevant.
Meta states that the click ID comes from the referral object and must be stored with the conversation. The documentation also lists the permissions needed (whatsapp_business_management and whatsapp_business_manage_events, with advanced access, plus Marketing API access tier), so allow lead time if your app review has not been done.
Implementation details that decide whether it works:
- Deduplicate yourself. Meta's documentation says it does not deduplicate these events. Use a unique constraint on conversation id plus event name in your
stage_eventtable, and make retries idempotent. - Send close to real time. Use a queue that fires when the stage changes, with retries and a dead-letter list. Daily batch uploads delay learning for the ad system.
- Only send what happened in the thread. Meta says these events should represent interactions that occur in the messaging thread. A website form conversion belongs in web events.
- Do not send events with no click ID. An event that cannot be tied to an ad adds noise. Keep it in your CRM reporting.
Meta does not state a validity window for the click ID in the documentation above. Do not assume one, and do not hold events for weeks. Test with a staged click and watch for acceptance in Events Manager.




