Messaging Webhooks

Receive real-time notifications for inbound and outbound WhatsApp messaging activity, including received messages, delivery status changes, and Click-to-WhatsApp ad attribution.

Messaging webhooks allow your application to receive real-time updates whenever messages are sent, delivered, read, failed, or received through your DoubleTick WhatsApp channel.

These webhooks help you keep external systems synchronized with customer conversations, automate workflows, trigger notifications, update CRMs, and build custom reporting pipelines.

Events Covered

EventDescription
MESSAGE_RECEIVEDTriggered whenever a customer sends a message to your WhatsApp channel in an individual conversation.
MESSAGE_STATUS_UPDATETriggered whenever the status of an outbound message changes.
CALL_TO_WHATSAPP_MESSAGE_RECEIVEDTriggered when an inbound message carries Click-to-WhatsApp ad attribution.

A given inbound message triggers MESSAGE_RECEIVED and, when ad attribution is present, additionally triggers CALL_TO_WHATSAPP_MESSAGE_RECEIVED. Messages sent inside WhatsApp Groups trigger WA_GROUP_MESSAGE_RECEIVED instead — see WhatsApp Group Webhooks.


Message Received

Event type: MESSAGE_RECEIVED

This webhook is triggered whenever DoubleTick receives a message from a customer in an individual (non-group) WhatsApp conversation. Messages sent inside WhatsApp Groups instead trigger WA_GROUP_MESSAGE_RECEIVED, and messages carrying Click-to-WhatsApp ad attribution additionally trigger CALL_TO_WHATSAPP_MESSAGE_RECEIVED.

The webhook is sent for all supported inbound WhatsApp message types, including:

  • Text
  • Image
  • Video
  • Audio
  • Sticker
  • Document
  • Location
  • Contacts
  • Button Replies
  • Flow Responses

Sample Payload

{
  "to": "11111111111",
  "from": "919999999999",
  "messageId": "wamid.HBgMOTE4OTc5NDk5OTQ5FQIAEhggQUNGOEE3REIwOUFBMjc2QTk0Mzk3Q0FEMzEyQTY4NTQA",
  "dtMessageId": "8b4abc26-2ea9-4f7d-a49a-90015e8dd7b8",
  "receivedAt": "2026-06-04T11:57:57.796Z",
  "contact": {
    "name": "John Doe"
  },
  "callbackData": null,
  "dtPairedTemplateId": null,
  "dtPairedTemplateName": null,
  "dtPairedTemplateLanguage": null,
  "dtPairedMessageMetadata": null,
  "integrationType": "WHATSAPP",
  "message": {
    "type": "TEXT",
    "text": "Hello",
    "context": {}
  },
  "dtLastMessageId": "dt_last_message_id",
  "lastMessageOrigin": "USER",
  "isAgentOffline": false,
  "customerId": "customer_xxxxxxxxxx",
  "dtCustomerId": "customer_xxxxxxxxxx"
}

Common Fields

FieldTypeDescription
tostringWhatsApp Business number that received the message.
fromstringCustomer's WhatsApp number.
messageIdstringOriginal WhatsApp message identifier.
dtMessageIdstringDoubleTick message identifier.
receivedAtstringTimestamp when the message was received.
contact.namestringCustomer display name.
integrationTypestringChannel type. Currently WHATSAPP.
customerIdstringDoubleTick customer identifier.
dtCustomerIdstringSame value as customerId.
isAgentOfflinebooleanWhether the agent currently assigned to this conversation is offline at the time the message is received. false if no agent is currently assigned.
dtLastMessageIdstring | nullDoubleTick identifier of the previous message in the conversation. null if this is the first message.
lastMessageOriginstring | nullOrigin of the previous message. See values below.
callbackDatastring | nullCustom data attached to the outbound message the customer is replying to. null when not applicable.
dtPairedMessageMetadataobject | nullAdditional metadata associated with the paired outbound message. null when not applicable.
userIdstringMeta user identifier. Present only for messages from Meta Cloud API integrations where user identity data is provided.
parentUserIdstringParent Meta user identifier. Present only when provided by Meta's Cloud API.
usernamestringMeta username. Present only when provided by Meta's Cloud API.
metaBusinessPortfolioIdstringMeta Business Portfolio identifier. Present only when provided by Meta's Cloud API.

lastMessageOrigin Values

ValueDescription
USERThe previous message was sent by a DoubleTick agent
CUSTOMERThe previous message was sent by the customer
SYSTEMThe previous message was a system-generated notification
REMINDERThe previous message was a scheduled reminder sent by DoubleTick

Alias Fields

customerId and dtCustomerId always contain the same value. dtCustomerId is the canonical identifier — customerId is a legacy alias maintained for backwards compatibility. Use dtCustomerId in new integrations.

Phone number format: to and from in MESSAGE_RECEIVED do not include a + prefix (e.g. "918979499949"). Most other DoubleTick events use E.164 format with a + prefix. Do not apply blanket normalization across event types — check the format per event before parsing or storing.

Reply Context

When a customer replies to an existing message, the context object contains information about the original message.

{
  "context": {
    "from": "919999999999",
    "id": "msg_xxxxxxxxxx"
  }
}
FieldDescription
context.idDoubleTick ID of the message being replied to.
context.fromSender of the original message.

When the message is not a reply:

{
  "context": {}
}

Template Reply Information

If a customer replies to a template message, the following fields are populated:

{
  "dtPairedTemplateId": 123,
  "dtPairedTemplateName": "order_confirmation",
  "dtPairedTemplateLanguage": "en"
}
FieldDescription
dtPairedTemplateIdTemplate identifier.
dtPairedTemplateNameTemplate name.
dtPairedTemplateLanguageTemplate language.

These fields are null when the message is not related to a template.

Supported Message Types

Text Message

{
  "message": {
    "type": "TEXT",
    "text": "Hello",
    "context": {}
  }
}

Image Message

{
  "message": {
    "type": "IMAGE",
    "url": "https://cdn.doubletick.io/example.jpg",
    "caption": "Check this out",
    "context": {}
  }
}

Video Message

{
  "message": {
    "type": "VIDEO",
    "url": "https://cdn.doubletick.io/example.mp4",
    "caption": "Product Demo",
    "context": {}
  }
}

Audio Message

{
  "message": {
    "type": "AUDIO",
    "url": "https://cdn.doubletick.io/example.ogg",
    "context": {}
  }
}

Sticker Message

{
  "message": {
    "type": "STICKER",
    "url": "https://cdn.doubletick.io/example.webp",
    "context": {}
  }
}

Document Message

{
  "message": {
    "type": "DOCUMENT",
    "url": "https://cdn.doubletick.io/invoice.pdf",
    "fileName": "invoice.pdf",
    "caption": "Invoice",
    "context": {}
  }
}

Location Message

{
  "message": {
    "type": "LOCATION",
    "latitude": 28.6139,
    "longitude": 77.2090,
    "name": "Connaught Place",
    "address": "New Delhi, India",
    "context": {}
  }
}

Contacts Message

{
  "message": {
    "type": "CONTACTS",
    "contacts": [
      {
        "name": {
          "formatted_name": "Jane Doe",
          "first_name": "Jane",
          "last_name": "Doe"
        },
        "phones": [
          { "phone": "+919876543210", "type": "CELL" }
        ],
        "emails": [
          { "email": "[email protected]", "type": "WORK" }
        ]
      }
    ]
  }
}

Button Reply

{
  "message": {
    "type": "BUTTON",
    "text": "Yes",
    "payload": "CONFIRM_ORDER_V2",
    "id": "btn_confirm",
    "context": {}
  }
}

id is the button's unique identifier as defined in the template. payload is the developer-defined data string attached to the button — it can be set independently and is the field to use for routing logic. The two often have the same value in simple cases but can differ (e.g. id is a short key; payload carries structured data).

Flow Response

{
  "message": {
    "type": "FLOW",
    "flowResponse": [
      {
        "id": "customer_name",
        "value": "John Doe"
      }
    ],
    "context": {}
  }
}

Message Status Update

Event type: MESSAGE_STATUS_UPDATE

This webhook is triggered whenever the delivery status of an outbound message changes.

Supported status values:

  • SENT
  • DELIVERED
  • READ
  • FAILED

to field semantics: In MESSAGE_STATUS_UPDATE, to is the customer's phone number (the outbound message recipient) — not the WhatsApp Business number. This is the opposite of MESSAGE_RECEIVED, where to is the WABA number. Use wabaNumber when you need to identify the sending business account in this event.

SENT Example

{
  "messageId": "dt_message_id",
  "to": "919999999999",
  "wabaNumber": "+919000000000",
  "status": "SENT",
  "statusTimestamp": "2026-06-04T11:48:14.000Z",
  "customerName": "John Doe",
  "dtCustomerId": "customer_pvgpZUh3wg",
  "sentBy": "agent_01HX9abc123",
  "assignedTo": "agent_01HX9abc123",
  "message": {
    "type": "TEXT",
    "text": "Hello"
  }
}

DELIVERED Example

{
  "messageId": "dt_message_id",
  "to": "919999999999",
  "status": "DELIVERED",
  "statusTimestamp": "2026-06-04T11:48:16.000Z"
}

READ Example

{
  "messageId": "dt_message_id",
  "to": "919999999999",
  "status": "READ",
  "statusTimestamp": "2026-06-04T11:49:02.000Z"
}

FAILED Example

{
  "messageId": "dt_message_id",
  "to": "919999999999",
  "status": "FAILED",
  "statusTimestamp": "2026-06-04T11:48:14.000Z",
  "failMessage": "Message delivery failed",
  "message": {
    "type": "TEXT",
    "text": "Hello"
  }
}

Status Fields

FieldDescription
messageIdDoubleTick message identifier.
toRecipient phone number.
statusCurrent message status.
statusTimestampTimestamp of the status update.
messageAvailable for SENT and FAILED statuses.
failMessageAvailable only for FAILED status.
customerNameCustomer name when available.
dtCustomerIdDoubleTick customer identifier.
sentByOpaque identifier of the agent or system that sent the message. Not a display name — use for tracking only.
assignedToOpaque identifier of the agent the conversation is currently assigned to at delivery time.
wabaNumberWhatsApp Business number used to send the message. Use this (not to) to identify the sending account.

Call to WhatsApp Message Received

Event type: CALL_TO_WHATSAPP_MESSAGE_RECEIVED

Fires whenever an inbound WhatsApp message carries Click-to-WhatsApp (CTWA) ad attribution data — i.e. the customer tapped a Meta ad or post and was taken directly into a WhatsApp conversation.

This event is an attribution companion to MESSAGE_RECEIVED, not a replacement for it. Both events are emitted for the same inbound message whenever ad attribution is present.

Trigger Conditions

The event is emitted when:

  • An inbound message is received in an individual (non-group) WhatsApp conversation.
  • The message includes ad-click attribution metadata supplied by the messaging provider.

It is not emitted for:

  • Messages without ad attribution (the ordinary MESSAGE_RECEIVED event still fires for those).
  • Messages received inside WhatsApp Groups (see WA_GROUP_MESSAGE_RECEIVED).

Sample Payload

{
  "to": "11111111111",
  "from": "919999999999",
  "messageId": "wa_message_id",
  "dtMessageId": "dt_message_id",
  "receivedAt": "2026-06-04T11:57:57.796Z",
  "contact": {
    "name": "John Doe"
  },
  "integrationType": "WHATSAPP",
  "message": {
    "type": "TEXT",
    "text": "Hi, I'm interested",
    "context": {}
  },
  "dtLastMessageId": null,
  "lastMessageOrigin": "USER",
  "isAgentOffline": false,
  "customerId": "customer_pvgpZUh3wg",
  "dtCustomerId": "customer_pvgpZUh3wg",
  "referralObject": {
    "source_url": "https://www.facebook.com/ads/123456789",
    "source_type": "ad",
    "source_id": "123456789",
    "headline": "Get 20% off today",
    "body": "Chat with us to claim your discount",
    "ctwa_clid": "ARAkLgKMnpFbFoAbXABC123",
    "media_type": "image",
    "image_url": "https://cdn.example.com/ad-image.jpg"
  }
}

Field Reference

This payload reuses the same base structure as MESSAGE_RECEIVED (see the Common Fields table above: to, from, messageId, dtMessageId, receivedAt, contact.name, integrationType, message, customerId, dtCustomerId, isAgentOffline, dtLastMessageId, lastMessageOrigin, callbackData, dtPairedTemplateId, dtPairedTemplateName, dtPairedTemplateLanguage, dtPairedMessageMetadata) and adds one additional required field:

FieldTypeRequiredDescription
referralObjectobjectYesAd attribution data for the Click-to-WhatsApp ad or post that brought the customer into the conversation.

referralObject

The exact shape of referralObject varies slightly depending on the WhatsApp provider behind the integration.

Common Fields (all providers)

FieldTypeDescription
source_urlstringURL of the ad or post the customer clicked.
source_typestringOrigin type: ad or post.
source_idstringMeta identifier for the ad or post.
headlinestringAd headline.
bodystringAd body text.
ctwa_clidstringClick-to-WhatsApp tracking identifier.

Media Attribution — Cloud API

FieldTypeCondition
media_typestringPresent when media attribution exists.
image_urlstringPresent when media_type is image.
video_urlstringPresent when media_type is video.
thumbnail_urlstringPresent when media_type is video.

Media Attribution — Gupshup / 360dialog

FieldTypeCondition
imageobjectPresent when the ad used an image. Shape: { "id": "..." }
videoobjectPresent when the ad used a video. Shape: { "id": "..." }

Conditional Fields

FieldCondition
image_urlPresent only for image-based ad attribution (Cloud API).
video_urlPresent only for video-based ad attribution (Cloud API).
thumbnail_urlPresent only for video-based ad attribution (Cloud API).
image / videoPresent only on Gupshup / 360dialog integrations, in place of the Cloud API media fields.

Notes

  • This event is not deduplicated. Unlike NEW_LEAD, it fires every time an ad-attributed message arrives — including repeat messages from a returning customer who keeps clicking ads.
  • This event always co-occurs with MESSAGE_RECEIVED for the same inbound message.
  • If the message is also the customer's first-ever message to your WhatsApp Business Account, NEW_LEAD is also emitted, with its own referral object and isCTWA: true.
  • This event is supported across all WhatsApp provider integrations.
  • This event is never emitted for messages sent inside WhatsApp Groups.

Edge Cases

Multiple Events Per Message

A single inbound ad-attributed message can generate up to three webhook events:

Customer taps a WhatsApp ad and sends a message
            │
            ├── MESSAGE_RECEIVED
            ├── CALL_TO_WHATSAPP_MESSAGE_RECEIVED
            └── NEW_LEAD (only if this is the customer's first-ever message)

Each event serves a different purpose:

  • MESSAGE_RECEIVED tracks the inbound message itself.
  • CALL_TO_WHATSAPP_MESSAGE_RECEIVED provides ad attribution for that specific message.
  • NEW_LEAD marks the customer's lifetime first contact.

Returning Customers

A customer who has messaged before but clicks a new ad and sends another message will trigger CALL_TO_WHATSAPP_MESSAGE_RECEIVED again — NEW_LEAD will not fire a second time.

Best Practices

  • Use dtMessageId to correlate this event with the corresponding MESSAGE_RECEIVED event for the same message.
  • Do not rely on the format of ctwa_clid — it is an opaque value generated by Meta.
  • Branch your handling logic on which media fields are present (image_url/video_url vs image/video) rather than assuming a single shape across all integrations.