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

# Webhooks

> Configure e receba mensagens

Trabalhe com os webhooks do Loce Zap com segurança: valide assinatura, saiba o que cada evento envia e quais limites se aplicam em cada plano.

## Visão geral

* Webhooks chegam sempre assinados com sua própria `apiKey`. Valide a assinatura antes de processar.
* Use `express.raw` somente na rota de webhook para preservar o corpo bruto.
* Planos free recebem apenas `SESSION-CONNECTED` e `SESSION-DISCONNECTED`. Eventos `MESSAGE-*` são exclusivos de planos pagos com `webhookMessages = true`.
* As tipagens `WebhookEvent`/`WebhookPayloadMap` entregam autocomplete imediato: explore `event.payload` na IDE para descobrir os campos mais recentes.

## Exemplo de implementação (Express)

```ts theme={null}
import express from "express";
import { LoceZap } from "loce-zap-sdk";

const app = express();
const zap = new LoceZap({ apiKey: process.env.LOCE_ZAP_API_KEY! });

app.post(
  "/webhooks/loce-zap",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const rawBody = req.body;

    if (!zap.webhooks.verifySignature({ headers: req.headers as any, rawBody })) {
      return res.status(401).json({ error: "invalid signature" });
    }

    const event = zap.webhooks.parseEvent(rawBody);

    switch (event.type) {
      case "SESSION-CONNECTED":
        console.log(`Sessão pronta: ${event.payload.name}`);
        break;
      case "MESSAGE-RECEIVED":
        console.log(
          `Msg de ${event.payload.sender.phone}:`,
          event.payload.message?.text
        );
        break;
      case "MESSAGE-SENT":
        console.log(`Mensagem ${event.payload.messageId} enviada`);
        console.log(
          `Recomendado: Armazenar o ${event.payload.key} para futuras identificações em webhooks`
        );
        break;
    }

    res.status(200).json({ received: true });
  }
);
```

## Exemplos rápidos de cada evento

Todos seguem o mesmo envelope `{ apiKey, sessionId, type, payload }` enviado pelo backend:

### SESSION-CONNECTED

```json theme={null}
{ "apiKey": "123", "sessionId": "sessao-1", "type": "SESSION-CONNECTED", "payload": { "id": "sessao-1", "name": "Minha sessão", "phone": "5564999999999" } }
```

### SESSION-DISCONNECTED

```json theme={null}
{ "apiKey": "123", "sessionId": "sessao-1", "type": "SESSION-DISCONNECTED", "payload": { "id": "sessao-1", "name": "Minha sessão" } }
```

### MESSAGE-RECEIVED

Inclui respostas de botão/lista, edições/deleções e reações já normalizadas.

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "MESSAGE-RECEIVED",
  "payload": {
    "key": "3A...",
    "fromMe": false,
    "sender": { "phone": "5564999999999", "name": "Contato", "profileImage": null },
    "type": "conversation",
    "eventType": "MESSAGE-RECEIVED",
    "referenceKey": null,
    "message": "Olá",
    "timestamp": "2024-01-01T12:00:00.000Z",
    "isBroadcast": false,
    "historic": false,
    "responseButtonId": null
  }
}
```

#### Template hidratado (contas Business)

Templates enviados por contas Business chegam com `type: "templateMessage"`. O texto (título, corpo e rodapé, quando presentes) é normalizado no campo `message`. Se o template tiver botões, eles vêm em `interact` com `type: "templateButtons"`, onde cada botão é `quickReply` (`id`), `url` (`url`) ou `call` (`phoneNumber`).

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "MESSAGE-RECEIVED",
  "payload": {
    "key": "3A...",
    "fromMe": false,
    "sender": { "phone": "5564999999999", "name": "Contato", "profileImage": null },
    "type": "templateMessage",
    "eventType": "MESSAGE-RECEIVED",
    "referenceKey": null,
    "message": "Título\n\nCorpo da mensagem\n\nRodapé",
    "interact": {
      "type": "templateButtons",
      "buttons": [
        { "type": "url", "displayText": "Abrir site", "url": "https://exemplo.com" },
        { "type": "quickReply", "displayText": "Falar com atendente", "id": "op1" }
      ]
    },
    "timestamp": "2024-01-01T12:00:00.000Z",
    "isBroadcast": false,
    "historic": false,
    "responseButtonId": null
  }
}
```

### Importante sobre `key`

No evento `MESSAGE-SENT`, o `payload.key` é o ID principal da mensagem (string). Guarde esse valor: ele aparece como `key`/`referenceKey` nos eventos seguintes (`MESSAGE-DELIVERED/READ/DELETED/EDITED/REACTION`) e permite correlacionar reações, edições e deleções com a mensagem original.

### MESSAGE-SENT

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "MESSAGE-SENT",
  "payload": {
    "messageId": "665f...",
    "key": "BAE...",
    "timestamp": "2024-01-01T12:00:00.000Z"
  }
}
```

### MESSAGE-DELIVERED / MESSAGE-READ

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "MESSAGE-DELIVERED",
  "payload": {
    "key": "BAE...",
    "fromMe": true,
    "timestamp": "2024-01-01T12:01:00.000Z"
  }
}
```

### MESSAGE-DELETED

Eventos normalizados.

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "MESSAGE-DELETED",
  "payload": {
    "key": "BAE...",
    "fromMe": true,
    "eventType": "MESSAGE-DELETED",
    "referenceKey": "BAE...",
    "timestamp": "2024-01-01T12:02:00.000Z"
  }
}
```

### MESSAGE-EDITED

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "MESSAGE-EDITED",
  "payload": {
    "key": "BAE...",
    "fromMe": true,
    "eventType": "MESSAGE-EDITED",
    "referenceKey": "BAE...",
    "message": "Nova mensagem",
    "timestamp": "2024-01-01T12:02:00.000Z"
  }
}
```

### MESSAGE-DISCARDED

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "MESSAGE-DISCARDED",
  "payload": {
    "messageId": "65b3...",
    "lastError": "Não foi possível acessar a URL de mídia, verifique se não expirou."
  }
}
```

### REACTION-MESSAGE

Normalizado como `NormalizedWebhookMessage`.

```json theme={null}
{
  "apiKey": "123",
  "sessionId": "sessao-1",
  "type": "REACTION-MESSAGE",
  "payload": {
    "key": "3A...",
    "fromMe": true,
    "eventType": "REACTION-MESSAGE",
    "referenceKey": "BAE...",
    "message": "👍",
    "timestamp": "2024-01-01T12:03:00.000Z"
  }
}
```

## Payloads por evento

| Evento                 | Payload                                                                                                                           |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `SESSION-CONNECTED`    | `{ id, name, phone }`                                                                                                             |
| `SESSION-DISCONNECTED` | `{ id, name }`                                                                                                                    |
| `MESSAGE-RECEIVED`     | `NormalizedWebhookMessage` (`key/fromMe/sender`, `message` ou `mediaUrl/filename`, `type`, `eventType/referenceKey`, `timestamp`) |
| `MESSAGE-SENT`         | `{ messageId, key, timestamp }`                                                                                                   |
| `MESSAGE-DELIVERED`    | `{ key, fromMe, timestamp }`                                                                                                      |
| `MESSAGE-READ`         | `{ key, fromMe, timestamp }`                                                                                                      |
| `MESSAGE-DISCARDED`    | `{ messageId, lastError }`                                                                                                        |
| `MESSAGE-EDITED`       | `NormalizedWebhookMessage` (`eventType=MESSAGE-EDITED`, `referenceKey`, conteúdo editado)                                         |
| `MESSAGE-DELETED`      | `NormalizedWebhookMessage` (`eventType=MESSAGE-DELETED`, `referenceKey`)                                                          |
| `REACTION-MESSAGE`     | `NormalizedWebhookMessage` (`eventType=REACTION-MESSAGE`, `referenceKey`, reaction text)                                          |

`NormalizedWebhookMessage` resume: `key`, `fromMe`, `sender` (phone/name/profileImage), `type`, `eventType`, `referenceKey`, `message` ou `mediaUrl/filename/latitude/longitude/interact`, `timestamp`, `isBroadcast`, `historic`, `responseButtonId`, etc. Em templates hidratados (`type: "templateMessage"`), o campo `interact` traz `{ type: "templateButtons", buttons: [...] }` quando o template tem botões.

## Notas adicionais

* URLs de mídia (`payload.mediaUrl`) expiram em até 24h. Baixe imediatamente se precisar armazenar.
* Para testes locais você pode gerar o cabeçalho com `zap.webhooks.signPayload(rawBody)` e enviar `x-locezap-signature`/`x-locezap-timestamp` manualmente.
