SocialMate Local API
Webhook events
The full catalogue of 35 events, which 9 are available on Free, and sample payloads.
SocialMate emits 35 webhook events. The Free tier may subscribe to
9 of them (marked Free below); Pro
unlocks all 35. Each event is wrapped in the standard payload envelope documented on
Webhooks — the fields here are
what you'll find under data.
Messaging
message.receivedFree — an inbound message. Carriesmetadataforpoll,locationandcontactmessages, andreplyTowhen it quotes another message.message.sentFree — an outbound message was handed to WhatsApp. It does not mean the message arrived — see delivery receipts below.message.delivered— a message you sent reached the recipient's phone (two grey ticks).messageIdis the id of your outbound message. See delivery receipts.message.read— the recipient opened a message you sent (two blue ticks). Only fires if that contact has read receipts switched on — see delivery receipts for what this can and can't tell you.message.reaction— someone reacted to a message (yours or theirs).emojiis the reaction;removed: truewith an emptyemojimeans they withdrew it. WhatsApp allows one reaction per person per message, so a new emoji replaces the old.poll.vote— someone voted on a poll you sent.selectedOptionsholds the option labels (not indices) andtallythe running count. Only polls sent from this account can be decrypted — votes on a poll someone else created are not readable.
Groups
group.participants_updated— someone joined, left, was promoted or was demoted.actionis one ofadd,remove,promote,demote,modify.
Accounts
account.connectedFree — a number came online.account.disconnectedFree — a number dropped offline.account.banned— WhatsApp banned the number.contacts.updated— the contact list changed.account.danger_mode_enabled— per-account rate-limit override turned on (Pro — High-Volume Mode).account.danger_mode_disabled— the override turned off.
Tunnel
tunnel.started— the Cloudflare tunnel came up.tunnel.url_changedFree — the public URL changed (Quick tunnels rotate).tunnel.stoppedFree — the tunnel went down.
Sync
sync.started— a history backfill began.sync.completed— a backfill finished.sync.failed— a backfill errored.
Media
media.discovered— new media was seen in a chat.media.downloaded— a media file finished downloading.media.failed— a media download failed.media.deleted— a media file was removed.media.context_updated— your AI agent saved a cached description/transcript for a media item (Agent Memory). Payload carries the media block withcontextandneedsContext:false.
Smart queue
queue.item.enqueued— an item entered the queue.queue.item.processing— the worker picked it up.queue.item.sent— the item sent successfully.queue.item.failed— the item failed (after retries).queue.item.cancelled— the item was cancelled.queue.batch.created— a batch import was created.queue.batch.completed— a batch finished.queue.batch.cancelled— a batch was cancelled.
License
license.activatedFree — a license activated.license.deactivatedFree — a license deactivated / a trial demoted.license.tier_changedFree — the entitlement tier changed.
Delivery receipts — message.delivered & message.read
message.sent only means the message was handed to WhatsApp. It never
tells you it actually landed. These two events close that loop, which is exactly what a
notification workflow needs: an automation that fires an order confirmation or a
shipping update can now learn whether the customer really received it — and whether they opened
it — instead of assuming.
message.delivered— WhatsApp reported the message reached the recipient's phone. Two grey ticks.message.read— the recipient opened it. Two blue ticks.
Both ship the same data shape. Correlate on messageId — it is the id of
the message you sent, the one the send API handed back:
{
"accountId": "a1b2c3",
"account": { "id": "a1b2c3", "phone": "15551234567", "name": "Sales line" },
"chatId": "[email protected]",
"chat": { "id": "[email protected]", "name": "Jane", "type": "individual" },
"messageId": "3EB0…",
"status": "delivered",
"timestamp": 1750000000000
}
status is "delivered" or "read" and always matches the event
name, so one receiver can switch on either.
Each fires at most once per message, on a forward step through
sent → delivered → read. WhatsApp re-sends acknowledgements liberally; SocialMate only
emits when the status actually moved forward, so a re-sent ack can't fan out duplicate deliveries and
a receipt never goes backwards.
What these can't tell you. Read them as evidence, never as proof.
message.read is the recipient's setting, not yours. Any WhatsApp
user can turn read receipts off. When they have, they can read your message and you will
never receive a message.read for it. The absence of the event is not evidence
the message went unread — don't build "they ignored us" logic on it, and don't show anyone a
"seen" status you cannot actually see.
There is no delivery guarantee. message.delivered reports only what
WhatsApp told us. If the recipient's phone is off or offline the event simply doesn't arrive yet —
it may land much later, or never. A missing message.delivered means "not confirmed",
not "failed".
Both events describe messages you sent, and nothing else — neither one reports on inbound messages.
Sample payload — message.received
{
"version": 1,
"event": "message.received",
"timestamp": "2026-06-03T12:00:00.000Z",
"tunnelUrl": "https://your-name.trycloudflare.com",
"data": {
"accountId": "a1b2c3",
"account": { "id": "a1b2c3", "phone": "15551234567", "name": "Sales line" },
"chatId": "[email protected]",
"chat": { "id": "[email protected]", "name": "Jane", "type": "individual" },
"messageId": "3EB0…",
"fromMe": false,
"timestamp": 1717245600000,
"body": "Hi, is this still available?",
"sender": { "id": "[email protected]", "name": "Jane", "phone": "15559876543" },
"media": null
}
}
The media slice
media is null for a text message. When the message carries media, the
slice arrives already downloaded — state: "downloaded" with local
apiPath / thumbnailApiPath routes you fetch through your tunnel (join them
with the payload's tunnelUrl). The payload never contains the WhatsApp CDN URL.
"media": {
"id": "med_1",
"kind": "image",
"mimeType": "image/jpeg",
"fileName": "invoice.jpg",
"fileSize": 81920,
"caption": "Your invoice",
"width": 1280,
"height": 720,
"durationSec": null,
"isVoice": false,
"isAnimated": false,
"state": "downloaded",
"hasThumbnail": true,
"apiPath": "/v1/accounts/a1b2c3/media/med_1/file",
"thumbnailApiPath": "/v1/accounts/a1b2c3/media/med_1/thumbnail"
}
Media deliveries are deferred. Webhooks that carry media —
message.received, message.sent, and media.discovered — are
held until the media download settles (usually a few seconds, with a ~2-minute backstop), so the
payload ships a ready-to-fetch local apiPath. If a download is genuinely stuck, the
event still fires after the backstop with the row's current state
(apiPath stays null until downloaded). Text-only messages fire
immediately.
Tip. Build one receiver that switches on event, verify the
signature first, then branch. Use the in-app Test button (or
POST /v1/webhooks/{id}/test) to inspect the exact shape before you build against it.