> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pilotstatus.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive WhatsApp Messages and Delivery Events via Webhooks

> Set up a webhook endpoint to receive inbound WhatsApp messages and delivery events from Pilot Status in real time, with Node.js and Python examples.

To receive inbound WhatsApp messages and delivery events in real time, register a webhook: an HTTPS endpoint on your system that Pilot Status calls with a JSON `POST` for each event.

## Create a webhook

<Tabs>
  <Tab title="Dashboard">
    Go to the **Webhooks** page (`/webhooks`) in the dashboard, click **New Webhook**, enter your URL, and pick the events you want. Webhooks are scoped per number.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    curl -X POST "https://pilotstatus.com.br/v1/webhooks" \
      -H "x-api-key: ps_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://example.com/hooks/whatsapp",
        "name": "My hook",
        "events": ["message.received", "message.sent", "message.delivered"]
      }'
    ```

    Full CRUD is available: `GET /v1/webhooks`, `GET`/`PATCH`/`DELETE /v1/webhooks/{id}`, and `GET /v1/webhooks/{id}/logs` for recent delivery attempts. With a **number-scoped** key the webhook attaches to that key's number; with a **tenant-scoped** key pass `whatsappNumberId` in the body (or the `x-whatsapp-number-id` header).
  </Tab>
</Tabs>

<Warning>
  Event gating is explicit: an **empty `events` list dispatches nothing** — there is no implicit "subscribe to all". Use `"*"` to receive every event. On `PATCH`, `events` **replaces** the entire list (no merge), and the webhook's number cannot be changed after creation.
</Warning>

To pause deliveries without deleting, `PATCH` with `{ "active": false }`. The signing `secret` is never returned by any endpoint.

## Example payload — `message.received`

```json theme={null}
{
  "event": "message.received",
  "data": {
    "from": "+5511999999999",
    "to": "+5511888888888",
    "numberId": "cmm0abc123",
    "messageId": "msg_in_id",
    "id": "cmm04obm46zz0qv4ycjp8x6r2",
    "type": "text",
    "fromMe": false,
    "content": "Hi",
    "participantName": "WhatsApp name",
    "createdAt": "2026-02-24T10:30:00.000Z"
  }
}
```

For media messages, `mediaLink`, `mediaType`, `mediaCaption`, and `mediaFilename` appear when the provider exposes them (e.g. Meta Cloud API). See the [Events reference](/api/webhooks/events) for every event and field.

## Receiver examples

Return `200` quickly and process the event asynchronously; deduplicate by `data.id` since retries can deliver an event more than once.

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  import express from "express";

  const app = express();
  app.use(express.json());

  app.post("/hooks/whatsapp", (req, res) => {
    const { event, data } = req.body;

    // Acknowledge immediately, process async
    res.sendStatus(200);

    if (event === "message.received") {
      console.log(`Message from ${data.from}: ${data.content}`);
      // enqueue reply, run your bot, etc.
    }
  });

  app.listen(3000);
  ```

  ```python Python (Flask) theme={null}
  from flask import Flask, request

  app = Flask(__name__)

  @app.post("/hooks/whatsapp")
  def whatsapp_hook():
      payload = request.get_json()
      event = payload.get("event")
      data = payload.get("data", {})

      if event == "message.received":
          print(f"Message from {data.get('from')}: {data.get('content')}")
          # enqueue reply, run your bot, etc.

      return "", 200

  if __name__ == "__main__":
      app.run(port=3000)
  ```
</CodeGroup>

## Replying to a received message

Reply with `POST /v1/messages/send` using the sender's number as the destination:

```bash theme={null}
curl -X POST "https://pilotstatus.com.br/v1/messages/send" \
  -H "x-api-key: ps_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "text": "Thanks, we got your message!", "destinationNumber": "+5511999999999" }'
```

<Note>
  `message.reply` fires when a contact quotes one of your messages — use `quotedMessageId` to match it against the `messageId` from your original `message.sent`. `message.read` only fires when the recipient has read receipts enabled.
</Note>

## Related

* [Webhooks concept](/concepts/webhooks)
* [Webhook Events reference](/api/webhooks/events)
* [Send Messages](/guides/send-messages)
