# Requisições de webhook

> O formato exato das requisições de webhook do Emailit, do método e dos cabeçalhos ao array JSON de eventos, além de ordenação, duplicatas e como responder.

Esta página descreve o que chega ao seu endpoint: a requisição HTTP, os cabeçalhos, o corpo JSON e o que o Emailit espera como resposta. Use-a ao escrever ou depurar um handler de webhook.

## Requisição

| Propriedade | Valor |
| --- | --- |
| Método | `POST` |
| Corpo | Um array JSON de 1 a 100 eventos, compacto (sem espaços extras) |
| Timeout | 30 segundos |
| Redirecionamentos | Não são seguidos. Uma resposta `3xx` conta como falha. |
| Concorrência | Uma requisição por vez por webhook. Os lotes são enviados um após o outro. |
| Latência | O Emailit verifica se há eventos novos a cada poucos segundos, então a maioria dos eventos chega em segundos. |

```http
POST /webhooks/emailit HTTP/1.1
Host: acme.com
Content-Type: application/json
User-Agent: Emailit-Webhooks/2.0
X-Emailit-Signature: 7242b580f77ec488f8ac21b57f092e3db61a431694f13074c7b4c85a4dd04be3
X-Emailit-Timestamp: 1790846045
```

## Cabeçalhos

| Cabeçalho | Descrição |
| --- | --- |
| `Content-Type` | Sempre `application/json`. |
| `User-Agent` | Sempre `Emailit-Webhooks/2.0`. |
| `X-Emailit-Signature` | HMAC-SHA256 em hexadecimal de `{timestamp}.{raw body}`, calculado com o segredo completo do webhook (incluindo `whsec_`). Consulte [Assinatura da requisição](/pt/docs/webhooks/request-signature/). |
| `X-Emailit-Timestamp` | Timestamp Unix, em segundos, do momento em que o Emailit assinou a requisição. Cada tentativa, incluindo as novas tentativas, é assinada de novo com um timestamp novo. |

## Corpo

O corpo é sempre um array JSON, mesmo quando contém um único evento. Cada elemento tem este formato:

| Campo | Tipo | Descrição |
| --- | --- | --- |
| `event_id` | string | O ID do evento, que começa com `evt_`. O mesmo evento mantém o mesmo ID em todas as novas tentativas. Eventos de teste começam com `evt_test_`. |
| `type` | string | O [tipo de evento](/pt/docs/webhooks/event-types/), como `email.delivered`. |
| `object` | objeto | O recurso a que o evento se refere. |
| `data` | objeto | Contém `object` de novo. `data.object` e o `object` de nível superior são idênticos; leia o que preferir. |

Os campos dentro de `object` dependem do tipo. Eventos de status de e-mail descrevem o e-mail; `email.clicked` e `email.loaded` descrevem o clique ou o carregamento e trazem o e-mail aninhado; eventos de recursos, como `contact.created`, descrevem o recurso.

### Exemplo de lote

Esta requisição traz dois eventos: uma entrega e, depois, um clique em um link do mesmo e-mail.

```json
[
  {
    "event_id": "evt_2xGk7Nq1VbD5sR8tLmW3eYhC6aP",
    "type": "email.delivered",
    "object": {
      "id": "em_2xGk7Lr3XcB8pQ1wYzK4dTfG9hJ",
      "object": "email",
      "from": "billing@acme.com",
      "to": "ada@example.com",
      "subject": "Your receipt #1042",
      "status": "delivered",
      "meta": { "order_id": "1042" },
      "updated_at": "2026-10-01T09:14:03.512000+00:00",
      "created_at": "2026-10-01T09:14:01.207000+00:00"
    },
    "data": {
      "object": {
        "id": "em_2xGk7Lr3XcB8pQ1wYzK4dTfG9hJ",
        "object": "email",
        "from": "billing@acme.com",
        "to": "ada@example.com",
        "subject": "Your receipt #1042",
        "status": "delivered",
        "meta": { "order_id": "1042" },
        "updated_at": "2026-10-01T09:14:03.512000+00:00",
        "created_at": "2026-10-01T09:14:01.207000+00:00"
      }
    }
  },
  {
    "event_id": "evt_2xGkC8w2TnF4yR7bMqL1sVdJ6pE",
    "type": "email.clicked",
    "object": {
      "id": "click_2xGkC7v5PmD3xQ9aLnK2rUcH8oW",
      "object": "click",
      "email_id": "em_2xGk7Lr3XcB8pQ1wYzK4dTfG9hJ",
      "email": {
        "id": "em_2xGk7Lr3XcB8pQ1wYzK4dTfG9hJ",
        "rcpt_to": "ada@example.com",
        "mail_from": "billing@acme.com",
        "subject": "Your receipt #1042",
        "created_at": "2026-10-01T09:14:01.207000+00:00",
        "campaign": null,
        "meta": { "order_id": "1042" }
      },
      "link": {
        "id": "link_2xGk7Mb6QwE1zS8cNpT4vXfK3gB",
        "url": "https://acme.com/orders/1042"
      },
      "contact": { "id": "con_2xFh3Ka9LmP2nR6tVwB8cDqY5sZ", "email": "ada@example.com" },
      "ip_address": "203.0.113.24",
      "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)",
      "created_at": "2026-10-01T09:20:47.884000+00:00"
    },
    "data": {
      "object": {
        "id": "click_2xGkC7v5PmD3xQ9aLnK2rUcH8oW",
        "object": "click",
        "email_id": "em_2xGk7Lr3XcB8pQ1wYzK4dTfG9hJ",
        "email": {
          "id": "em_2xGk7Lr3XcB8pQ1wYzK4dTfG9hJ",
          "rcpt_to": "ada@example.com",
          "mail_from": "billing@acme.com",
          "subject": "Your receipt #1042",
          "created_at": "2026-10-01T09:14:01.207000+00:00",
          "campaign": null,
          "meta": { "order_id": "1042" }
        },
        "link": {
          "id": "link_2xGk7Mb6QwE1zS8cNpT4vXfK3gB",
          "url": "https://acme.com/orders/1042"
        },
        "contact": { "id": "con_2xFh3Ka9LmP2nR6tVwB8cDqY5sZ", "email": "ada@example.com" },
        "ip_address": "203.0.113.24",
        "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)",
        "created_at": "2026-10-01T09:20:47.884000+00:00"
      }
    }
  }
]
```

Os timestamps estão em ISO 8601, em UTC. `meta` são os metadados que você anexou ao enviar, ou `null`. `contact` é `null` quando o destinatário não é um dos seus contatos, e `campaign` é `null` para e-mails enviados pela API e por SMTP.

## Ordenação

Dentro de uma requisição, os eventos vêm do mais antigo para o mais recente. Entre requisições, não há garantia de ordem: um lote que falhou é reenviado mais tarde, quando eventos mais novos podem já ter chegado, e uma nova tentativa manual pode reenviar eventos antigos depois dos novos.

Não conte com a ordem de chegada. Para eventos de e-mail, use o `status` e o `updated_at` do e-mail e mantenha o estado mais avançado que você já viu (por exemplo, não sobrescreva `clicked` com um `delivered` atrasado). Na dúvida, busque o estado atual com [Obter um e-mail](/pt/docs/api-reference/emails/get/).

## Duplicatas

O mesmo evento pode chegar mais de uma vez:

- Quando o seu endpoint dá timeout ou retorna um erro depois de processar parte de um lote, o lote inteiro é reenviado, incluindo os eventos que você já tratou.
- Quando você usa **Retry failed** ou tenta de novo uma única requisição.

Guarde cada `event_id` processado, com uma restrição de unicidade, e ignore os eventos que você já viu.

## Como responder

| A sua resposta | Resultado |
| --- | --- |
| Qualquer `2xx` em até 30 segundos | Todos os eventos do lote são marcados como **Delivered**. O corpo da resposta é ignorado. |
| `3xx`, `4xx` ou `5xx` | O lote falhou e é reenviado. |
| Nenhuma resposta em 30 segundos, erro de conexão, erro de TLS | O lote falhou e é reenviado. O painel mostra o código de status `0`. |

Nas requisições com falha, o Emailit guarda os primeiros 2.000 caracteres do corpo da sua resposta. Você pode lê-los na aba **Requests** do webhook, por isso vale a pena retornar uma mensagem de erro curta para facilitar a depuração.

Recomendações:

- **Verifique e confirme o recebimento rápido.** Confira a assinatura, coloque os eventos em uma fila ou em uma tabela e retorne `200`. Faça o trabalho demorado, como chamar outras APIs, em um job em segundo plano.
- **Retorne `2xx` para os eventos que você ignora.** Caso contrário, eles são reenviados por dias.
- **Retorne `401` ou `403` para uma assinatura inválida.** A requisição é reenviada como qualquer falha, o que dá tempo para corrigir um segredo mal configurado.

Consulte [Novas tentativas e falhas](/pt/docs/webhooks/retries-and-failures/) para ver o cronograma de novas tentativas e o que acontece quando um endpoint continua falhando.

## Veja também

  - [Assinatura da requisição](/pt/docs/webhooks/request-signature/)
  - [Tipos de evento](/pt/docs/webhooks/event-types/)

---
Fonte: https://emailit.com/pt/docs/webhooks/webhook-requests/
