Webhook events
Reference of the message, status, account and campaign variation payloads Msgera POSTs to your webhook URL.
Msgera POSTs a JSON payload to your configured webhook URL for every event your webhook is subscribed to. Message events are dispatched to either your personal or group webhook URL depending on whether the chat is a 1:1 chat or a group chat. Account status and campaign variation events always go to your personal URL. See Webhooks for how to configure URLs, subscriptions and filters, verify signatures, and handle retries.
Event types
Each webhook chooses which events it receives. Webhooks that existed before event subscriptions were introduced receive message.inbound only until you subscribe them to more.
| Field | Type | Description |
|---|---|---|
message.inbound | personal | group | A customer sent a message to one of your devices. |
message.status | personal | group | A message you sent moved to SENT, DELIVERED, READ or FAILED. |
message.edited | personal | group | A message in the chat was edited. |
message.deleted | personal | group | A message in the chat was deleted for everyone. |
message.reaction | personal | group | An emoji reaction was added, changed or removed. |
account.status | personal | A device connected, disconnected, needs a QR scan, or was restricted by WhatsApp. |
Every request also names its event in the X-Msgera-Event header, so you can route a delivery before parsing the body.
Envelope
All event types except message.inbound share one envelope. The event-specific fields live under data.
{
"event": "message.status",
"id": "ack_acc_7d1c9e_618D49720368605B82BBC72AA9CAE4ED_delivered",
"occurredAt": "2026-04-12T10:11:25.000Z",
"data": { ... }
}| Field | Type | Description |
|---|---|---|
event | string | Event type, for example message.status. |
id | string | Idempotency key of the event. The same underlying event always has the same id, across retries and redeliveries. Equal to the X-Msgera-Idempotency-Key header. |
occurredAt | string | ISO 8601 time the event happened. |
data | object | Event-specific fields, described below. |
message.inbound
Inbound messages keep their original flat payload, so existing integrations continue to work. The event, id and occurredAt fields are added alongside the message fields rather than wrapping them in data. Fields that do not apply to a message are omitted.
{
"accountId": "acc_7d1c9e",
"deviceUid": "dev_4f8c2a1e",
"from": "15551234567",
"fromName": "Zeyad",
"text": "When will my order arrive?",
"timestamp": "2026-04-12T10:11:23.000Z",
"providerMessageId": "618D49720368605B82BBC72AA9CAE4ED",
"isGroup": false,
"mediaId": null,
"event": "message.inbound",
"id": "msg_acc_7d1c9e_618D49720368605B82BBC72AA9CAE4ED",
"occurredAt": "2026-04-12T10:11:23.000Z"
}| Field | Type | Description |
|---|---|---|
event | string | Always message.inbound. |
id | string | Idempotency key: stable for this message across retries and redeliveries. Use it to dedupe. |
occurredAt | string | Same value as timestamp. |
accountId | string | Device the message arrived on. |
deviceUid | string | null | UID of that device, the same value you pass as deviceUid when sending. |
from | string | Sender phone number. |
fromName | string | Sender display name if WhatsApp exposed one. |
text | string | Message text or media caption. Omitted for media without a caption. |
mediaType | string | image | video | audio | document | sticker. Omitted for text messages. |
mediaMimeType | string | MIME type of the attachment, for example image/jpeg. |
mediaFileName | string | Original file name, when WhatsApp provides one (usually documents). |
mediaSize | number | Attachment size in bytes. |
isVoiceNote | boolean | Audio only: true when the audio was recorded as a voice note. |
mediaId | string | null | Id of the stored attachment in your Msgera media library, or null for text messages and attachments that could not be stored. |
timestamp | string | ISO 8601 timestamp the provider stamped on the message. |
providerMessageId | string | Underlying WhatsApp message id. Edit, delete, reaction and status events refer to messages by this id. |
isGroup | boolean | True when the message came from a group chat. |
groupId | string | Provider id of the group chat (only when isGroup is true). |
groupName | string | Display name of the chat, when known. |
Inbound media
Webhook payloads never contain the media bytes. Msgera stores each attachment of up to 16 MB in your media library and sends its mediaId together with mediaMimeType, mediaFileName and mediaSize. Look the asset up with the Media endpoints using mediaId. When an attachment could not be stored, mediaType is still set and mediaId is null.
What is not delivered
message.status
Sent when a message you sent from a device advances to a new delivery status. Statuses only move forward: once a message is READ you will not receive DELIVERED or SENT for it again, and a status that arrives late or repeats is not delivered. Each status of a message is sent at most once and has its own id.
{
"event": "message.status",
"id": "ack_acc_7d1c9e_618D49720368605B82BBC72AA9CAE4ED_delivered",
"occurredAt": "2026-04-12T10:11:25.000Z",
"data": {
"messageId": "5c2e9a7b-1d3f-4e8a-b6c4-9f0d2e1a3b5c",
"providerMessageId": "618D49720368605B82BBC72AA9CAE4ED",
"accountId": "acc_7d1c9e",
"status": "DELIVERED",
"to": "15551234567",
"isGroup": false,
"groupId": null,
"campaignId": null,
"error": null
}
}| Field | Type | Description |
|---|---|---|
messageId | string | Msgera message id. |
providerMessageId | string | Underlying WhatsApp message id. |
accountId | string | Device the message was sent from. |
status | string | SENT | DELIVERED | READ | FAILED. |
to | string | Recipient of the message. |
isGroup | boolean | True when the message was sent in a group chat. |
groupId | string | null | Group id for group messages, otherwise null. |
campaignId | string | null | Campaign the message belongs to, or null for direct sends. |
error | string | null | Failure reason when status is FAILED, otherwise null. |
message.edited
Sent when a message in the chat is edited. In a group, only edits by the original author are accepted.
{
"event": "message.edited",
"id": "edit_acc_7d1c9e_618D49720368605B82BBC72AA9CAE4ED_1775988720000",
"occurredAt": "2026-04-12T10:12:00.000Z",
"data": {
"messageId": "5c2e9a7b-1d3f-4e8a-b6c4-9f0d2e1a3b5c",
"providerMessageId": "618D49720368605B82BBC72AA9CAE4ED",
"accountId": "acc_7d1c9e",
"from": "15551234567",
"isGroup": false,
"groupId": null,
"text": "When will my order #1042 arrive?",
"previousText": "When will my order arrive?"
}
}message.deleted
Sent once when a message is deleted for everyone. In a group, only deletes by the original author are accepted.
{
"event": "message.deleted",
"id": "del_acc_7d1c9e_618D49720368605B82BBC72AA9CAE4ED",
"occurredAt": "2026-04-12T10:13:30.000Z",
"data": {
"messageId": "5c2e9a7b-1d3f-4e8a-b6c4-9f0d2e1a3b5c",
"providerMessageId": "618D49720368605B82BBC72AA9CAE4ED",
"accountId": "acc_7d1c9e",
"from": "15551234567",
"isGroup": false,
"groupId": null
}
}message.reaction
Sent when someone reacts to a message, changes their reaction, or removes it. A removed reaction has emoji set to null and removed set to true. Each person has at most one reaction per message; a newer reaction replaces the previous one.
{
"event": "message.reaction",
"id": "react_acc_7d1c9e_618D49720368605B82BBC72AA9CAE4ED_15551234567_1775988780000",
"occurredAt": "2026-04-12T10:13:00.000Z",
"data": {
"messageId": "5c2e9a7b-1d3f-4e8a-b6c4-9f0d2e1a3b5c",
"providerMessageId": "618D49720368605B82BBC72AA9CAE4ED",
"accountId": "acc_7d1c9e",
"from": "15551234567",
"isGroup": false,
"groupId": null,
"emoji": "👍",
"removed": false
}
}| Field | Type | Description |
|---|---|---|
messageId | string | Msgera id of the message that changed. It can be a message you received or one you sent. |
providerMessageId | string | WhatsApp id of the message that changed. |
accountId | string | Device the chat belongs to. |
from | string | Phone number of the person who edited, deleted or reacted. |
isGroup | boolean | True when the message is in a group chat. |
groupId | string | null | Group id for group chats, otherwise null. |
text | string | message.edited only: the new text. |
previousText | string | null | message.edited only: the text before this edit. |
emoji | string | null | message.reaction only: the reaction emoji, or null when the reaction was removed. |
removed | boolean | message.reaction only: true when the reaction was removed. |
account.status
Sent to your personal URL when a device's status changes. Filters do not apply to this event.
{
"event": "account.status",
"id": "acct_acc_7d1c9e_disconnected_42",
"occurredAt": "2026-04-12T11:02:17.000Z",
"data": {
"accountId": "acc_7d1c9e",
"status": "DISCONNECTED",
"phone": "15550001111",
"reasonCode": "SESSION_DISCONNECTED",
"previousStatus": "CONNECTED"
}
}| Field | Type | Description |
|---|---|---|
accountId | string | Device whose status changed. |
status | string | CONNECTED | CONNECTING | DISCONNECTED | QR_REQUIRED | BLOCKED for connection changes; RESTRICTED | UNRESTRICTED when WhatsApp temporarily restricts the device or lifts the restriction. |
phone | string | null | Phone number of the device, when known. |
reasonCode | string | null | Machine-readable reason, when available. |
previousStatus | string | Connection changes only: the status before this change. |
until | string | null | RESTRICTED only: when WhatsApp lifts the restriction, if known. |
reason | string | RESTRICTED and UNRESTRICTED only: why the device was restricted. |
Restricted devices pause campaigns
RESTRICTED, Msgera refuses new sends on it and pauses its running campaigns. They resume automatically when the restriction is lifted and an UNRESTRICTED event is sent.Campaign variation events
When a linked-device TEXT campaign needs more wording variety than its text provides, it waits in AWAITING_REVIEW while message variations are generated. Msgera notifies the personalwebhook URL of the campaign's primary device when generation finishes, so you can review and approve them with the Campaign Variations endpoints instead of polling. These events are delivered to every active webhook with a personal URL, regardless of its event subscriptions and filters, and are signed like every other delivery - see Verify the signature. They use their own flat payload; tell them apart by the event field.
{
"event": "campaign.variations_ready",
"campaignId": "uuid",
"campaignName": "Summer Sale",
"variationStatus": "PENDING_REVIEW",
"required": 19,
"capacity": 24,
"timestamp": "2026-05-22T10:02:41.000Z"
}| Field | Type | Description |
|---|---|---|
event | string | campaign.variations_ready or campaign.variations_failed. |
campaignId | string | Id of the campaign awaiting review. |
campaignName | string | Campaign name. |
variationStatus | string | PENDING_REVIEW when ready, FAILED when generation failed. |
required | number | Unique messages the audience needs: ceil(recipients / 8). |
capacity | number | Unique messages the approved options currently produce (variations_ready only). |
error | string | Why generation failed (variations_failed only). Retry with the regenerate endpoint or delete the campaign. |
timestamp | string | ISO 8601 time the event was emitted. |
Auto-approved campaigns
autoApproveVariations: true starts sending as soon as the generated options cover required, and no campaign.variations_ready event is sent. If they fall short, the campaign stays in review and the event is delivered as usual. Generation that fails, including when the monthly AI template generation quota is exhausted, always sends campaign.variations_failed.