Guides

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

FieldTypeNotes
message_idstringWhatsApp message id. Use it for media and receipts.
fromstringSender JID, e.g. 5511999999999@s.whatsapp.net.
tostringRecipient JID (the connected device for inbound).
contentstringText body or media caption. Omitted when empty.
message_typestringDiscriminator: text, image, audio, document, location, contact, and more.
timestampstringISO8601 timestamp of the message.
is_from_mebooleantrue for messages the device sent (echoes).
push_namestringSender'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_typeNested objectKey content
textcontent holds the text.
image / video / audio / stickermediamime_type, caption, file_length, media_path. Audio adds seconds / ptt.
documentdocumentfile_name, mime_type, page_count, media_path.
locationlocationdegrees_latitude, degrees_longitude, name, address.
contactcontactdisplay_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.jpg

The 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:

Statuserror / statusMeaning
404media_not_foundNo media is associated with that message id.
503device_disconnectedCache miss and no connected device to fetch with.
202retry_requested / retry_pendingCDN copy expired; WUTS asked the sender to re-upload. Retry shortly.
502download_failedThe 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

On this page