Skip to content
SocialMate

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.received Free — an inbound message. Carries metadata for poll, location and contact messages, and replyTo when it quotes another message.
  • message.sent Free — 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). messageId is 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). emoji is the reaction; removed: true with an empty emoji means 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. selectedOptions holds the option labels (not indices) and tally the 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. action is one of add, remove, promote, demote, modify.

Accounts

  • account.connected Free — a number came online.
  • account.disconnected Free — 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_changed Free — the public URL changed (Quick tunnels rotate).
  • tunnel.stopped Free — 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 with context and needsContext: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.activated Free — a license activated.
  • license.deactivated Free — a license deactivated / a trial demoted.
  • license.tier_changed Free — 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 downloadedstate: "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.

Free forever · no card Download free