Receiving Messages
Subscribe to inbound message webhooks, parse the payload, download media, and acknowledge with read receipts.
WUTS delivers inbound WhatsApp messages to your application in real time over webhooks. You register an HTTPS endpoint, subscribe it to message events, and WUTS POSTs a JSON envelope every time the connected device receives (or sends) a message. This guide walks through configuring the subscription, parsing the payload, downloading media, and acknowledging messages with a read receipt.
You need a connected device before any message events fire. See Connect a device and the Webhooks overview for the full event catalog.
1. Subscribe to message events
Register your endpoint with POST /webhook and include the events you care
about in enabled_events. For an inbound-message integration the relevant
events are message.received (and optionally message.sent,
message.delivered, message.read).
curl -X POST https://api.wuts.com.br/webhook \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-app.example.com/wuts/webhook",
"enabled_events": ["message.received", "message.delivered", "message.read"],
"enabled": true,
"secret": "a-shared-secret"
}'When you set a secret, every delivery carries an signature field
(sha256=<hex>) so you can verify it came from WUTS. See the
Webhooks page for the verification recipe.
2. The inbound webhook envelope
Every webhook is a POST with the same outer envelope. The message-specific
fields live under data.
{
"id": "event-uuid",
"event": "message.received",
"timestamp": 1640995200,
"device_id": "device-uuid",
"user_id": "user-uuid",
"data": {
"message_id": "3EB0F8A1B2C3D4E5F6",
"from": "5511999999999@s.whatsapp.net",
"to": "5511888888888@s.whatsapp.net",
"content": "Hello from WhatsApp!",
"message_type": "text",
"timestamp": "2026-06-15T12:00:00Z",
"is_from_me": false,
"push_name": "Jane Doe"
},
"signature": "sha256=..."
}Core fields in data
| Field | Type | Notes |
|---|---|---|
message_id | string | WhatsApp message id. Use it for media and receipts. |
from | string | Sender JID, e.g. 5511999999999@s.whatsapp.net. |
to | string | Recipient JID (the connected device for inbound). |
content | string | Text body or media caption. Omitted when empty. |
message_type | string | Discriminator: text, image, audio, document, location, contact, and more. |
timestamp | string | ISO8601 timestamp of the message. |
is_from_me | boolean | true for messages the device sent (echoes). |
push_name | string | Sender's WhatsApp display name, when available. |
Always check is_from_me. If you subscribe to message.sent, your own
outbound messages arrive here too — skip them in a bot to avoid reply loops.
Group messages
In a group, from is the group JID (for example
120363039000000000@g.us) and the actual sender is identified by the
participant field. Group-aware integrations should read participant to
attribute the message to a person. See
Group management.
3. Handling message types
Switch on message_type. Depending on the type, data carries an extra nested
object with the details:
message_type | Nested object | Key content |
|---|---|---|
text | — | content holds the text. |
image / video / audio / sticker | media | mime_type, caption, file_length, media_path. Audio adds seconds / ptt. |
document | document | file_name, mime_type, page_count, media_path. |
location | location | degrees_latitude, degrees_longitude, name, address. |
contact | contact | display_name, vcard. |
Example of an inbound image with a caption:
{
"event": "message.received",
"data": {
"message_id": "3EB0F8A1B2C3D4E5F6",
"from": "5511999999999@s.whatsapp.net",
"message_type": "image",
"timestamp": "2026-06-15T12:01:30Z",
"is_from_me": false,
"content": "Check this out",
"media": {
"mime_type": "image/jpeg",
"caption": "Check this out",
"file_length": 84213,
"width": 1280,
"height": 720,
"media_path": "/messages/3EB0F8A1B2C3D4E5F6/media"
}
}
}4. Download media
Media is not embedded in the webhook. The media / document object
includes media_path, a stable gateway path you fetch with your token. Call
GET /messages/:id/media to stream the raw bytes:
curl -L https://api.wuts.com.br/messages/3EB0F8A1B2C3D4E5F6/media \
-H "Authorization: Bearer <token>" \
--output inbound-image.jpgThe endpoint serves the warm-cached copy when available, otherwise it
re-downloads the file on demand through the connected device. The response is
the binary body with the original Content-Type (for example image/jpeg).
Possible non-2xx responses:
| Status | error / status | Meaning |
|---|---|---|
404 | media_not_found | No media is associated with that message id. |
503 | device_disconnected | Cache miss and no connected device to fetch with. |
202 | retry_requested / retry_pending | CDN copy expired; WUTS asked the sender to re-upload. Retry shortly. |
502 | download_failed | The download could not be completed. |
A 202 is not a failure. WhatsApp media URLs expire, so WUTS requests a
retransmission and returns 202. Wait a few seconds and retry the same
GET /messages/:id/media call. For the full media lifecycle see
Working with media.
5. Acknowledge with a read receipt
To mark inbound messages as read (the blue ticks for the sender), call
POST /messages/:id/read. In a group you must also pass sender_jid so the
receipt is attributed to the original sender.
curl -X POST https://api.wuts.com.br/messages/3EB0F8A1B2C3D4E5F6/read \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"chat_jid": "5511999999999@s.whatsapp.net",
"message_ids": ["3EB0F8A1B2C3D4E5F6"]
}'{
"success": true,
"chat_jid": "5511999999999@s.whatsapp.net",
"message_ids": ["3EB0F8A1B2C3D4E5F6"],
"receipt_type": "read",
"timestamp": "2026-06-15T12:02:00Z"
}message_ids is optional — when omitted, the :id from the route is used. To
acknowledge delivery only (two grey ticks, no blue), use the same body against
POST /messages/:id/delivered. See
Message receipts for receipt semantics.
6. End-to-end handler sketch
A minimal inbound handler in pseudocode:
on POST /wuts/webhook (envelope):
verify envelope.signature with your shared secret # reject if invalid
respond 200 immediately # ack fast, process async
if envelope.event != "message.received": return
msg = envelope.data
if msg.is_from_me: return # ignore own echoes
sender = msg.participant or msg.from # group-aware
switch msg.message_type:
case "text":
handleText(sender, msg.content)
case "image", "video", "audio", "document":
bytes = GET /messages/{msg.message_id}/media # 202 -> retry later
handleMedia(sender, msg, bytes)
case "location":
handleLocation(sender, msg.location)
default:
log("unhandled type", msg.message_type)
POST /messages/{msg.message_id}/read # mark as read
body: { chat_jid: msg.from, sender_jid: sender }Respond 2xx to the webhook quickly and do heavy work asynchronously. WUTS
retries non-2xx deliveries with backoff, so slow or failing handlers will
receive duplicate events. Treat delivery as at-least-once and dedupe on
message_id.
Next steps
- Working with media — download, caching, and retransmission details.
- Group management — resolving participants and group metadata.
- Webhooks — full event catalog and signature verification.
- Errors and Rate limits — handling failures robustly.