Skip to content

Expose CTWA referral metadata on message webhooks#58

Open
juliomuhlbauer wants to merge 2 commits intoevolution-foundation:mainfrom
juliomuhlbauer:codex/ctwa-referral-webhooks
Open

Expose CTWA referral metadata on message webhooks#58
juliomuhlbauer wants to merge 2 commits intoevolution-foundation:mainfrom
juliomuhlbauer:codex/ctwa-referral-webhooks

Conversation

@juliomuhlbauer
Copy link
Copy Markdown

@juliomuhlbauer juliomuhlbauer commented May 9, 2026

Summary

\n- expose raw CTWA / ad referral metadata as a top-level field on inbound message webhooks\n- persist the referral JSON on inbound message records without letting later read/delivered receipts wipe it out\n- add focused tests for referral extraction and message upsert behavior\n- document the new webhook field in the events system guide\n\n## Verification\n- go test ./...

Summary by Sourcery

Expose CTWA/ad referral metadata on inbound WhatsApp messages, surface it on webhooks, persist it on message records, and ensure it is preserved across message status updates.

New Features:

  • Include raw CTWA/ad referral metadata as a top-level referral field in inbound message webhook payloads.
  • Store referral metadata JSON on persisted inbound message records.

Enhancements:

  • Update message persistence logic to only overwrite the referral field when new referral data is provided, preserving existing metadata on subsequent status updates.
  • Add utilities to extract referral context info from different WhatsApp message types.

Build:

  • Bump GORM and add sqlite driver dependencies to support new repository tests.

Documentation:

  • Document the new data.referral field and its semantics in the events system guide.

Tests:

  • Add unit tests for referral extraction from messages using ExternalAdReply metadata.
  • Add repository tests to verify referral metadata is preserved when messages are upserted on status changes.

Summary by Sourcery

Expose and persist CTWA/ad referral metadata on inbound messages, surface it on webhooks, and add coverage and docs for the new behavior.

New Features:

  • Include raw CTWA/ad referral metadata as a top-level referral field in inbound message webhook payloads.
  • Persist referral metadata JSON on message records alongside core message attributes.

Enhancements:

  • Ensure message upserts preserve existing referral metadata when updating message status or timestamps.
  • Support extracting referral context from multiple WhatsApp message types via a shared context helper.

Build:

  • Update GORM and add SQLite driver dependencies to support repository testing.

Documentation:

  • Document the new data.referral field and its semantics in the events system guide.

Tests:

  • Add unit tests for extracting referral metadata from WhatsApp messages.
  • Add repository tests to verify referral data is retained across message status updates.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 9, 2026

Reviewer's Guide

Expose WhatsApp CTWA/ad referral metadata in inbound message webhooks and persist it on messages, ensuring it is preserved across status updates, with tests and docs added.

Sequence diagram for exposing CTWA referral on inbound message webhooks

sequenceDiagram
    actor User
    participant AdPlatform
    participant WhatsAppServer
    participant MyClient
    participant ReferralExtractor
    participant MessageRepository
    participant Database
    participant WebhookEndpoint

    User->>AdPlatform: Click CTWA ad
    AdPlatform->>WhatsAppServer: Open chat with CTWA metadata

    WhatsAppServer->>MyClient: Deliver inbound message with contextInfo.externalAdReply
    MyClient->>ReferralExtractor: extractReferralFromMessage(message)
    ReferralExtractor-->>MyClient: referral JSON

    MyClient->>MyClient: Build dataMap with message fields
    MyClient->>MyClient: Add referral to dataMap when present

    MyClient->>WebhookEndpoint: POST inbound message webhook
    Note over WebhookEndpoint: Payload contains data.referral

    MyClient->>MessageRepository: InsertMessage(message with Referral)
    MessageRepository->>Database: Upsert by message_id
    Database-->>MessageRepository: Insert or update
    MessageRepository-->>MyClient: Result

    loop Subsequent status updates
        WhatsAppServer->>MyClient: Deliver status update event
        MyClient->>ReferralExtractor: extractReferralFromMessage(message)
        ReferralExtractor-->>MyClient: nil referral
        MyClient->>MessageRepository: InsertMessage(message without Referral)
        MessageRepository->>Database: Upsert timestamp,status,source only
        Database-->>MessageRepository: Existing Referral preserved
    end
Loading

Entity relationship diagram for Message with referral metadata

erDiagram
    Message {
        uuid Id PK
        string MessageID "unique message id"
        string Timestamp "received timestamp"
        string Status "message status"
        string Source "message source user"
        jsonb Referral "raw CTWA referral metadata"
    }
Loading

Class diagram for Message persistence and referral extraction

classDiagram
    class Message {
        string Id
        string MessageID
        string Timestamp
        string Status
        string Source
        json.RawMessage Referral
        BeforeCreate(tx *gorm.DB) error
    }

    class messageRepository {
        *gorm.DB db
        InsertMessage(message message_model.Message) error
    }

    class MyClient {
        Config config
        messageRepository messageRepository
        myEventHandler(rawEvt interface_any)
    }

    class ReferralExtractor {
        extractReferralFromMessage(message *waE2E.Message) json.RawMessage
        getContextInfoFromMessage(message *waE2E.Message) *waE2E.ContextInfo
    }

    Message <.. messageRepository : uses
    MyClient --> messageRepository : has
    MyClient ..> ReferralExtractor : calls
    ReferralExtractor ..> waE2E.Message : reads
    ReferralExtractor ..> waE2E.ContextInfo : reads
Loading

File-Level Changes

Change Details Files
Expose referral metadata on inbound WhatsApp webhooks and persist it in stored messages.
  • Extract referral metadata from WhatsApp messages via contextInfo.externalAdReply using a helper function.
  • Attach the serialized referral JSON as data.referral in webhook payloads when present.
  • Persist inbound messages to the database with a Referral JSON column populated from the extracted metadata when DatabaseSaveMessages is enabled.
pkg/whatsmeow/service/whatsmeow.go
pkg/whatsmeow/service/referral.go
pkg/message/model/message_model.go
Ensure referral metadata is not overwritten on subsequent message status updates.
  • Change the upsert logic to dynamically choose which columns to update on conflict, only including referral when a new referral payload is provided.
  • Add a repository test that simulates an inbound message followed by a status update and asserts that referral JSON remains unchanged.
pkg/message/repository/message_repository.go
pkg/message/repository/message_repository_test.go
Add tests and documentation for referral behavior and dependencies for testing.
  • Add a unit test validating referral extraction from messages containing ExternalAdReply metadata.
  • Document the new data.referral field and its behavior in the events system guide.
  • Add sqlite driver and bump GORM to support the new repository tests.
pkg/whatsmeow/service/referral_test.go
docs/wiki/recursos-avancados/events-system.md
go.mod
go.sum

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

@juliomuhlbauer juliomuhlbauer marked this pull request as ready for review May 9, 2026 14:39
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

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, and left some high level feedback:

  • In myEventHandler, InsertMessage is executed in a goroutine and its error is ignored, which can hide persistence failures; consider at least logging the error or routing it through a channel so DB issues are observable.
  • The Referral field is annotated with gorm:"type:jsonb", which is Postgres-specific and may not map optimally (or at all) on other drivers like sqlite; consider letting GORM infer the type or using driver-specific tags/options to avoid cross-database issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `myEventHandler`, `InsertMessage` is executed in a goroutine and its error is ignored, which can hide persistence failures; consider at least logging the error or routing it through a channel so DB issues are observable.
- The `Referral` field is annotated with `gorm:"type:jsonb"`, which is Postgres-specific and may not map optimally (or at all) on other drivers like sqlite; consider letting GORM infer the type or using driver-specific tags/options to avoid cross-database issues.

## Individual Comments

### Comment 1
<location path="pkg/whatsmeow/service/whatsmeow.go" line_range="1523-1532" />
<code_context>

 		postMap["data"] = dataMap

+		if mycli.config.DatabaseSaveMessages {
+			message := message_model.Message{
+				MessageID: evt.Info.ID,
+				Timestamp: evt.Info.Timestamp.Format("2006-01-02 15:04:05"),
+				Status:    "Received",
+				Source:    evt.Info.Chat.ToNonAD().User,
+				Referral:  referral,
+			}
+
+			go mycli.messageRepository.InsertMessage(message)
+		}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider handling or at least logging errors from async InsertMessage calls

Calling `InsertMessage` in a goroutine without checking its error means DB failures (connection issues, constraints, etc.) will be completely silent. Please either log the error inside the goroutine or use a small helper to capture and log it so we don’t risk undetected write failures.
</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 pkg/whatsmeow/service/whatsmeow.go Outdated
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