Skip to content

feat(chatwoot): capture ad-referral metadata from WhatsApp ads - #2683

Open
edneymatias wants to merge 2 commits into
evolution-foundation:mainfrom
edneymatias:feat/whatsapp-ad-referral-attribution
Open

feat(chatwoot): capture ad-referral metadata from WhatsApp ads#2683
edneymatias wants to merge 2 commits into
evolution-foundation:mainfrom
edneymatias:feat/whatsapp-ad-referral-attribution

Conversation

@edneymatias

@edneymatias edneymatias commented Aug 11, 2026

Copy link
Copy Markdown

What changed

Extracts sourceId, sourceType, mediaType and mediaUrl from externalAdReply (WhatsApp click-to-WhatsApp ad replies) alongside the existing title/body/thumbnailUrl/sourceUrl fields, and exposes them as a normalized referral object under the outgoing message's content_attributes when relaying to Chatwoot.

Also hardens the ads-thumbnail pipeline: Chatwoot's ads-thumbnail image is frequently served as webp by Facebook, and Jimp's core package ships no webp codec. A failed decode previously threw an unhandled exception that aborted message delivery after the Chatwoot conversation shell had already been created — leaving an empty, orphaned conversation behind. Both the thumbnail download and the Jimp resize now fall back gracefully (sending the raw, unresized image) instead of failing the whole send.

How to test

  1. Configure a WhatsApp inbox with the Chatwoot integration enabled.
  2. Reply to (or click through from) a Facebook/Instagram click-to-WhatsApp ad so the incoming message carries an externalAdReply context.
  3. Confirm in Chatwoot that:
    • The conversation and message are created successfully (including when the ad thumbnail is webp-encoded).
    • The message's content_attributes.referral includes source_id, source_type, source_url, headline, body, media_type, and image_url where available in the source ad payload.

Summary by Sourcery

Capture WhatsApp ad referral metadata and propagate it to Chatwoot while hardening ad thumbnail handling to avoid message delivery failures.

New Features:

  • Expose normalized referral metadata from WhatsApp externalAdReply payloads into Chatwoot message content attributes.

Bug Fixes:

  • Prevent message delivery failures and orphaned conversations by gracefully handling failures when downloading or processing ad thumbnails.

Enhancements:

  • Unify extraction of externalAdReply fields and build structured referral attributes for downstream consumers.

…d webp thumbnail fallback

Extracts sourceId, sourceType, mediaType and mediaUrl from externalAdReply
alongside the existing title/body/thumbnailUrl/sourceUrl fields, and exposes
them as a normalized referral object under the outgoing message's
content_attributes.

Also hardens the ads-thumbnail pipeline: a failed webp decode in Jimp (Jimp
core has no webp codec) previously threw an unhandled exception that aborted
message delivery after the Chatwoot conversation shell had already been
created, leaving an empty conversation behind. Both the thumbnail download
and the Jimp resize now fall back gracefully (raw, unresized image) instead
of failing the whole send.
@sourcery-ai

sourcery-ai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Extends Chatwoot WhatsApp integration to capture richer ad referral metadata from WhatsApp externalAdReply payloads and attach it as normalized referral content_attributes on outgoing messages, while hardening the ads-thumbnail download and image processing pipeline to fail gracefully instead of aborting message delivery.

Flow diagram for building referral content_attributes from externalAdReply

flowchart TD
  A[Receive WhatsApp message with externalAdReply] --> B[Extract externalAdReply from msg and msg.message variants]
  B --> C[Build adsMessage with title, body, thumbnailUrl, sourceUrl, sourceId, sourceType, mediaType, mediaUrl]
  C --> D[Call buildReferralAttributes to map adsMessage to referral]
  D --> E[Initialize contentAttributes]
  E --> F[Add replyToIds from getReplyToIds if present]
  F --> G[Add referral to contentAttributes if referral exists]
  G --> H{contentAttributes not empty?}
  H -->|Yes| I[Append JSON stringified content_attributes to outgoing Chatwoot message]
  H -->|No| J[Send Chatwoot message without content_attributes]
Loading

Flow diagram for resilient ads thumbnail download and processing

flowchart TD
  A[Detect adsMessage with thumbnailUrl] --> B[Attempt thumbnail download with axios.get]
  B --> C{Download succeeds?}
  C -->|No| D[Log warning Failed to download ads thumbnail]
  D --> E[Skip thumbnail upload and continue message delivery]
  C -->|Yes| F[Determine mimeType and generate file name]
  F --> G[Create fileData buffer from response]
  G --> H[Set processedBuffer = fileData]
  H --> I[Try to read and resize image with Jimp.read and img.cover]
  I --> J{Jimp processing succeeds?}
  J -->|No| K[Log warning Failed to process ads thumbnail with Jimp, sending raw image]
  K --> L[Use processedBuffer = original fileData]
  J -->|Yes| M[Set processedBuffer from img.getBuffer]
  L --> N[Create Readable stream from processedBuffer]
  M --> N[Create Readable stream from processedBuffer]
  N --> O[Attach thumbnail file to Chatwoot message and send]
Loading

File-Level Changes

Change Details Files
Capture WhatsApp externalAdReply metadata and send it to Chatwoot as normalized referral content_attributes.
  • Extend sendMessageToChatwoot signature to accept a referral object and merge it into content_attributes alongside reply-to metadata.
  • Refactor content_attributes construction to build a single object (replyToIds + referral) and append it only when non-empty.
  • Normalize WhatsApp Ads externalAdReply fields into a single adsMessage object by resolving the first available externalAdReply from multiple possible locations in the message payload.
  • Introduce buildReferralAttributes helper to map adsMessage fields to Chatwoot referral schema (source_id, source_type, source_url, headline, body, media_type, image_url) and return undefined when empty.
  • Wire buildReferralAttributes into the ads send path so referral data is propagated on outgoing Chatwoot messages.
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
Harden ads-thumbnail download and image processing to avoid breaking message delivery when thumbnails cannot be fetched or decoded (e.g., webp).
  • Wrap axios thumbnail download in try/catch, log a warning, and short-circuit thumbnail upload when the download fails.
  • Default processedBuffer to the raw downloaded bytes and wrap Jimp.read/cover/getBuffer in try/catch, logging a warning and sending the raw image when Jimp processing fails.
  • Ensure thumbnail upload continues using a Readable stream even when image processing falls back to the original file data.
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts

Possibly linked issues

  • #fix: Ad messages fail to sync to Chatwoot due to unprotected thumbnail download: PR wraps ad thumbnail download/processing in try/catch, preventing failures that caused ad messages to be lost in Chatwoot.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts" line_range="2258-2262" />
<code_context>
         if (isAdsMessage) {
-          const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' });
+          let imgBuffer;
+          try {
+            imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' });
+          } catch (error) {
+            this.logger.warn(`Failed to download ads thumbnail: ${error?.message || error}`);
+            return;
+          }

</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid skipping message delivery when thumbnail download fails

With the early `return` in the catch block, any failure to fetch the ads thumbnail prevents the message (and its referral metadata) from being sent. This makes message delivery depend on thumbnail availability and can reduce reliability. Instead, log the error and proceed with sending the message without the thumbnail/attachment when the image fetch fails.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts Outdated
… fails

Previously, a failed thumbnail download or unresolved mimetype aborted
the entire ad message send, silently dropping both the text content
and the referral metadata. Now falls back to a text-only message via
createMessage(), which gains an optional referral parameter to carry
the attribution data through that path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant