Skip to content

Cloud API: messages[0].referral (Click-to-WhatsApp ctwa_clid) dropped in messages.upsert — never reaches webhook #2645

Description

@carlosanpardo

Welcome!

  • Yes, I have searched for similar issues on GitHub and found none.

What did you do?

Environment

  • Evolution API: v2.3.7 (Docker, evoapicloud/evolution-api:latest)
  • Connection type: WhatsApp Cloud API (Business, not Baileys/QR)
  • Consumer: webhook → n8n

Summary

When a user starts a conversation by tapping "Send message" on a
Click-to-WhatsApp (CTWA) ad, Meta includes a referral object (with
ctwa_clid, source_id, etc.) inside messages[0] of the incoming
webhook payload. Evolution receives this correctly, but the object it
emits on messages.upsert drops the referral entirely: contextInfo
comes out undefined and there is no field carrying the ad attribution.
As a result, CTWA click IDs cannot be captured, breaking ad-conversion
attribution.

What Meta sends (inbound to Evolution — confirmed in logs)

"messages": [{
  "referral": {
    "source_url": "https://fb.me/XXXX",
    "source_id": "1202XXXXXXXXXXXXX",
    "source_type": "ad",
    "headline": "...",
    "body": "...",
    "media_type": "image",
    "image_url": "...",
    "ctwa_clid": ""
  },
  "from": "",
  "id": "wamid...",
  "timestamp": "...",
  "text": { "body": "Hola 👋 ..." },
  "type": "text"
}]

What Evolution emits on messages.upsert (the bug)

{
  "key": { "id": "wamid...", "remoteJid": "@s.whatsapp.net", "fromMe": false },
  "pushName": "JL",
  "message": { "conversation": "Hola 👋 ..." },
  "contextInfo": undefined,          // <-- referral lost
  "messageType": "conversation",
  ...
}

What did you expect?

Expected

The referral data (at minimum ctwa_clid and source_id) should be
propagated to the emitted message object — e.g. inside contextInfo
and/or a dedicated adReferral field — so downstream consumers can
capture ad attribution.

Steps to reproduce

  1. Configure a WhatsApp Cloud API instance.
  2. Create a Click-to-WhatsApp ad in Meta Ads Manager.
  3. From a phone number that has never messaged this WABA before, tap
    "Send message" on the ad and send the first message.
  4. Observe the inbound payload contains messages[0].referral with
    ctwa_clid, but the messages.upsert webhook has contextInfo: undefined and no referral data.

Note: ctwa_clid is only present on the first message from a given
user; subsequent messages don't carry it. Use a fresh number to test.

What did you observe instead of what you expected?

Looking at the section "What Evolution emits on messages.upsert (the bug)" — that JSON block is the observed behavior. But you're right to flag it: in a GitHub bug report the reviewer scans for a crisp "Expected vs Observed" pair, and mine buried the observed part inside a code block without a plain-language sentence stating it. Let me give you that sentence so the report reads cleanly.

Observed: The messages.upsert event fires with contextInfo: undefined and no adReferral (or any other) field carrying the referral. The ctwa_clid, source_id, and the rest of messages[0].referral that Meta sent are silently dropped — they never reach the webhook consumer, so ad-conversion attribution is impossible.
Expected: The referral data (at minimum ctwa_clid and source_id) should be propagated to the emitted object, so downstream consumers can capture the CTWA attribution.
You can drop those two lines in verbatim, right above the two JSON blocks, so a reviewer gets the contrast in one read before diving into the payloads.

One caveat worth repeating, since it affects whether "observed" is even still true: this was observed on v2.3.7. A later release changelog mentions work on the Cloud API sender-identification path in the same handler, so before you post, confirm the drop still happens on the current version — otherwise the observed behavior may already be fixed and the report would close as resolved.

Screenshots/Videos

No response

Which version of the API are you using?

2.3.7

Other environment specifications

No response

If applicable, paste the log output

No response

Additional Notes

Additional context

There is a second, related crash on the same handler: WhatsApp status
notifications (sent/read) enter messageHandle with a contacts
array that has no profile, causing
TypeError: Cannot read properties of undefined (reading 'name') at
e.contacts[0].profile.name. Guarding that access with optional
chaining (e.contacts[0]?.profile?.name) resolves it.

I've validated a local patch that (a) reads messages[0].referral and
attaches it as adReferral + merges into contextInfo, and (b) guards
the profile access above. Happy to open a PR against the current
main if the maintainers find it useful — I'd just want to confirm the
issue isn't already addressed in a version newer than 2.3.7 first.

Temporary workaround (until fixed upstream)

Since the bug is in the compiled dist/main.js bundle, I patched it
directly and bind-mounted the patched file over the container's copy,
so it survives container recreation (I'm on Docker Swarm, where
docker restart recreates the replica from the base image and loses
any in-container change).

What the patch does, right before the Cloud API's
sendDataWebhook("messages.upsert", ...) inside messageHandle:

  1. Reads messages[0].referral and attaches it as an adReferral
    object, also merging it into contextInfo:
   if (referral) {
     msg.adReferral = {
       ctwaClid:  referral.ctwa_clid  ?? null,
       sourceId:  referral.source_id  ?? null,
       sourceUrl: referral.source_url ?? null,
       sourceType:referral.source_type?? null,
       headline:  referral.headline   ?? null,
       body:      referral.body       ?? null,
       mediaType: referral.media_type ?? null,
       imageUrl:  referral.image_url  ?? null
     };
     msg.contextInfo = { ...(msg.contextInfo || {}), ...msg.adReferral };
   }
  1. Guards the profile access that crashes on status notifications:
    e.contacts[0].profile.namee.contacts[0]?.profile?.name
    (same guard applied to the .profile.phone accesses further down).

Bind-mount (docker-compose / stack):

volumes:
  - /opt/evolution-patch/main.js:/evolution/dist/main.js:ro

After redeploying, the messages.upsert payload now carries
data.adReferral.ctwaClid and data.contextInfo.ctwaClid, and the
status-notification TypeError is gone. Verified end-to-end against a
real CTWA ad click.

Note: this is a bundle-level patch, so it must be re-applied whenever
the image is updated. A proper fix in the TypeScript source
(whatsapp.business.service.ts) would be preferable — happy to open a
PR if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions