# Receiving WhatsApp messages

> Receive the messages customers send to your WhatsApp numbers, read their files, and reply within the 24-hour service window.

When a customer writes to one of your connected WhatsApp numbers, Nexiom Connect stores the message and sends it to your [webhook](/docs/webhooks/whatsapp/received). You decide what happens next: answer automatically, pass the conversation to your team, or record the customer's choice. Receiving messages is free: received messages never count toward your plan.

## Set up receiving

### Subscribe to the webhook

In the dashboard, add a webhook endpoint to your project and select **whatsapp.message.received**. See [Webhooks](/docs/webhooks).

### Create an API key with full access

Reading received messages and their files with the API needs a key with `whatsapp.full_access`. A key with only `whatsapp.send` can still reply and [mark messages as read](/docs/api/messaging/read), but received messages return `404` to it. See [API keys](/docs/learn/api-keys#permissions).

### Handle each message

Verify the webhook signature, store the message by its `data.id`, then act on its `type`. Deduplicate by the envelope `id`: an event can be delivered more than once.

Every received message also appears in **Messaging → Logs**, with **Received** as its direction. From a received message's page, you can reply while the service window is open, and mark it as read.

## What customers can send

The webhook's `data.type` names the field that holds the message.

| `type` | What the customer did | Content |
| --- | --- | --- |
| `text` | Wrote a message. | `text.body` |
| `image`, `video`, `audio`, `document`, `sticker` | Sent a file or a voice message. | A [media object](/docs/api/messaging/retrieve#the-media-object), with the caption and file details |
| `location` | Shared a location. | `location.latitude`, `location.longitude`, `location.name`, `location.address` |
| `contacts` | Shared one or more contact cards. | `contacts[]`, with names, phones, and emails |
| `button` | Tapped a quick-reply button of your template. | `button.text`, and `button.payload`: the value you sent for that button |
| `interactive` | Tapped a reply button, or chose a row of a list, from your interactive message. | `interactive.kind` (`button_reply` or `list_reply`), `interactive.id`, `interactive.title` |
| `reaction` | Reacted to a message with an emoji, or removed a reaction. | `reaction.emoji`, `reaction.message_id` |
| `unsupported` | Sent something Nexiom Connect doesn't read yet, such as an order. | `unsupported.provider_type` |

When the customer replies to a specific message, `reply_to` holds its ID. A conversation the customer started from a Click-to-WhatsApp ad or post includes `referral` on its first message, with the ad or post they came from.

## Files customers send

Nexiom Connect downloads each file from WhatsApp and stores it privately, then sends the webhook. By the time you receive `whatsapp.message.received`, the file is ready to download.

| Media `status` | Meaning |
| --- | --- |
| `pending` | The file is still being downloaded. Only visible if you [retrieve the message](/docs/api/messaging/retrieve) before the webhook arrives. |
| `ready` | The file is stored. Download it with [Get message media](/docs/api/messaging/media). |
| `unavailable` | WhatsApp no longer had the file, it was larger than 100 MB, or it couldn't be downloaded after retries for up to 24 hours. The webhook still fires, so no message goes unnoticed. |

Download a file with a short-lived URL:

```shell
curl "https://api-connect.nxiom.com/api/v1/messaging/messages/msg_example/media" \
  -H "Authorization: Bearer $NEXIOM_API_KEY"
```

The response's `url` works for 5 minutes by default, up to 1 hour with `expiresInSeconds`. Request a new one each time you need the file.

## The 24-hour service window

A customer's message opens a 24-hour customer service window between your number and that customer, counted from when they sent it. Each new message from them extends it. While it is open, you can reply with any message type: text, media, a location, buttons, a list, or a reaction. Once it closes, you can only send a [template](/docs/whatsapp/templates), and a free-form message returns `422` with the code `whatsapp_service_window_closed`.

[Retrieve a received message](/docs/api/messaging/retrieve#the-chat-object) to see its conversation's window: `chat.service_window_expires_at`, and `chat.can_send_free_form`, which becomes `false` one minute before the window closes. See [The customer service window](/docs/whatsapp/key-concepts#customer-service-window).

## Reply to a customer

Send a reply with [Send a message](/docs/api/messaging/send): `from` is your number that received the message (`data.recipient` of the webhook), and `to` is the customer (`data.sender`). Add `reply_to` to show the customer's message quoted above yours.

```json
{
  "channel": "whatsapp",
  "from": "+213555000111",
  "to": "+213555123456",
  "reply_to": "msg_example",
  "text": {
    "body": "Sorry about that, Amina. We're sending a replacement today."
  }
}
```

Replies are messages you send: they need an `Idempotency-Key`, follow the same [statuses](/docs/whatsapp/sending#message-statuses) and webhooks as templates, and count toward your plan like any message you send. See [Costs](/docs/whatsapp/sending#costs).

## Mark messages as read

[Mark a message as read](/docs/api/messaging/read) to show the customer blue ticks. Add `"typing_indicator": true` to also show that you are typing while you prepare the answer.

```shell
curl -X POST "https://api-connect.nxiom.com/api/v1/messaging/messages/msg_example/read" \
  -H "Authorization: Bearer $NEXIOM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "typing_indicator": true }'
```

## Confirm an order with quick replies

### Send a template with quick replies

Create a utility template with **Confirm** and **Cancel** quick-reply buttons. When you send it, give each button a payload that identifies the order:

```json
{
  "template": {
    "id": "mtp_example",
    "variables": { "body": { "1": "Amina", "2": "A-1234" } },
    "buttons": [
      { "index": 0, "value": "confirm-A-1234" },
      { "index": 1, "value": "cancel-A-1234" }
    ]
  }
}
```

### Receive the tap

When the customer taps a button, you receive `whatsapp.message.received` with `type` set to `button`, `button.payload` set to `confirm-A-1234` or `cancel-A-1234`, and `reply_to` set to your template message's ID.

### Update the order and reply

Update the order from the payload. The tap opened the service window, so you can answer with a text message that thanks the customer and confirms the next step.

## Answer questions with a menu

### Acknowledge the message

When a `text` message arrives, [mark it as read](/docs/api/messaging/read) with the typing indicator.

### Offer choices

Reply with an [interactive list](/docs/api/messaging/send#interactive) of topics, such as **Track my order** and **Return an item**, each with its own row `id`.

### Route the answer

The customer's choice arrives with `type` set to `interactive`, `interactive.kind` set to `list_reply`, and the row's `id`. Answer it automatically, or pass the conversation to your team.

## Collect a photo or document

Ask the customer to send a photo of a damaged item or a scanned document. When the message arrives, its file is already stored: [get its URL](/docs/api/messaging/media), download it, and keep it in your own storage if you need it for longer than 30 days.

## Retention

Received messages, their content, and their files are kept for 30 days, like the messages you send. After that, they are deleted and the API returns `404`. Store what you need in your own system when you receive it.

## Next steps

- [whatsapp.message.received](/docs/webhooks/whatsapp/received) The webhook payload and its fields.
- [Sending messages](/docs/whatsapp/sending) Templates, replies, statuses, and costs.
- [Send a message](/docs/api/messaging/send) Every message type and its fields.

---

Source: https://connect.nxiom.com/docs/whatsapp/receiving
