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

# Webhooks

### O que são Webhooks?

**Webhooks** são uma maneira eficiente e automatizada de uma aplicação enviar dados em tempo real para outra aplicação. Eles permitem que sistemas diferentes comuniquem eventos específicos sem a necessidade de uma solicitação ativa da aplicação receptora. Em vez disso, a aplicação que gera o evento envia uma notificação, normalmente na forma de uma solicitação HTTP POST, para uma URL previamente configurada pela aplicação receptora.

### O que os Webhooks fazem?

Os Webhooks são usados para notificar sua aplicação sobre eventos que ocorrem em outra aplicação ou serviço. Algumas das tarefas comuns realizadas por Webhooks incluem:

* **Notificações em Tempo Real**: A aplicação receptora é imediatamente notificada quando algo acontece, como a criação de um novo pedido, uma mudança de status, ou uma nova mensagem.

* **Automação de Processos**: Permitem automatizar respostas ou ações em sua aplicação quando certos eventos ocorrem, sem a necessidade de consultas constantes à API.

* **Integração entre Sistemas**: Facilitam a integração entre sistemas diferentes, permitindo que eventos em um sistema desencadeiem ações automáticas em outro.

### Como os Webhooks funcionam?

1. **Configuração**: Primeiro, a aplicação receptora deve configurar um endpoint (uma URL pública) que estará pronto para receber as notificações via Webhook.

2. **Registro do Webhook**: A aplicação emissora precisa ser configurada para enviar notificações para o endpoint do Webhook sempre que um evento específico ocorrer.

3. **Envio do Evento**: Quando o evento configurado ocorre, a aplicação emissora envia uma solicitação HTTP POST para o endpoint do Webhook, incluindo no corpo da requisição os dados relevantes sobre o evento.

4. **Processamento do Evento**: A aplicação receptora processa a informação recebida e pode executar várias ações em resposta ao evento, como atualizar um banco de dados, enviar um e-mail, ou disparar outro processo interno.

```mermaid theme={null}
sequenceDiagram
    participant Zapster as Zapster API
    participant Webhook as Endpoint Webhook
    Zapster->>Webhook: Envia Evento (HTTP POST)
    Webhook-->>Zapster: Resposta (200 OK / Erro >= 400)
    Zapster->>Webhook: Reenvio em caso de erro (até 5 vezes)
```

### Um webhook, várias instâncias

Um webhook na Zapster é um destino que você cadastra uma vez (a URL que vai receber os eventos) e reaproveita em quantas instâncias quiser. Cada instância decide, por conta própria, quais eventos quer receber naquele destino.

Vale entender dois conceitos que trabalham juntos:

* **O webhook (na sua conta)**: guarda a URL, um nome e o status (ligado ou desligado). É o endereço para onde as notificações são enviadas. Ele pertence à sua conta, não a uma instância específica.
* **A associação com cada instância**: toda instância que usa o webhook ganha sua própria associação. É nela que ficam os eventos assinados e os ajustes daquela instância (ativar/desativar, modo de teste). Um mesmo webhook pode estar associado a várias instâncias ao mesmo tempo.

```mermaid theme={null}
flowchart LR
    W["Webhook<br/>(URL + nome + status)"]
    W --> A["Instância A<br/>eventos: message.received"]
    W --> B["Instância B<br/>eventos: message.sent, message.read"]
    W --> C["Instância C<br/>eventos: instance.connected"]
```

#### Por que compartilhar um webhook

O principal motivo é manutenção centralizada. Se você tem dezenas de instâncias enviando eventos para o mesmo sistema, cadastrar a URL uma única vez e reutilizá-la evita repetir configuração. Quando a URL precisar mudar (troca de servidor, novo domínio, ajuste de rota), você altera em um só lugar e a mudança vale para todas as instâncias que usam aquele webhook.

```mermaid theme={null}
flowchart LR
    E["Você edita a URL<br/>uma única vez"] --> W["Webhook"]
    W --> A["Instância A"]
    W --> B["Instância B"]
    W --> C["Instância C"]
```

#### Como fazer na prática

O mesmo endpoint (`POST /wa/instances/:id/webhooks`) cobre os dois caminhos. A diferença está no corpo da requisição:

* Envie **`url`** para criar um webhook novo e já associá-lo à instância. Use na primeira instância.
* Envie **`webhook_id`** para reutilizar um webhook que já existe. Use nas demais instâncias.

<Warning>
  O corpo aceita **`url`** OU **`webhook_id`**, nunca os dois juntos. Se ambos forem enviados, a Zapster prioriza o `webhook_id`. O campo `events` é sempre obrigatório e precisa ter pelo menos um evento.
</Warning>

**1. Primeira instância: crie o webhook com `url`**

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks \
    -H "Authorization: Bearer SEU_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://sua-app.com/webhook",
      "name": "Webhook de produção",
      "events": ["message.received", "message.sent"]
    }'
  ```

  ```javascript JavaScript (client) theme={null}
  const response = await fetch(
    'https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks',
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer SEU_TOKEN',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        url: 'https://sua-app.com/webhook',
        name: 'Webhook de produção',
        events: ['message.received', 'message.sent'],
      }),
    },
  );

  const data = await response.json();
  console.log(data);
  // A resposta traz o id do webhook criado, que você reaproveita nas próximas instâncias.
  // { "webhook_id": "2nenz69l0xbf0m3uu9tfo", ... }
  ```

  ```javascript JavaScript (server) theme={null}
  // Node.js 18+ (fetch nativo). Guarde o token em variável de ambiente.
  const response = await fetch(
    'https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.ZAPSTER_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        url: 'https://sua-app.com/webhook',
        name: 'Webhook de produção',
        events: ['message.received', 'message.sent'],
      }),
    },
  );

  const { webhook_id } = await response.json();
  console.log('Webhook criado:', webhook_id);
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks');
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer SEU_TOKEN',
          'Content-Type: application/json',
      ],
      CURLOPT_POSTFIELDS => json_encode([
          'url' => 'https://sua-app.com/webhook',
          'name' => 'Webhook de produção',
          'events' => ['message.received', 'message.sent'],
      ]),
  ]);

  $response = curl_exec($ch);
  curl_close($ch);

  $data = json_decode($response, true);
  echo $data['webhook_id'];
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	body, _ := json.Marshal(map[string]any{
  		"url":    "https://sua-app.com/webhook",
  		"name":   "Webhook de produção",
  		"events": []string{"message.received", "message.sent"},
  	})

  	req, _ := http.NewRequest(
  		http.MethodPost,
  		"https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks",
  		bytes.NewReader(body),
  	)
  	req.Header.Set("Authorization", "Bearer SEU_TOKEN")
  	req.Header.Set("Content-Type", "application/json")

  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	defer resp.Body.Close()

  	data, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(data))
  }
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks",
      headers={"Authorization": "Bearer SEU_TOKEN"},
      json={
          "url": "https://sua-app.com/webhook",
          "name": "Webhook de produção",
          "events": ["message.received", "message.sent"],
      },
  )

  data = response.json()
  print(data["webhook_id"])
  ```

  ```java Java theme={null}
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;

  public class CreateWebhook {
      public static void main(String[] args) throws Exception {
          String body = """
              {
                "url": "https://sua-app.com/webhook",
                "name": "Webhook de produção",
                "events": ["message.received", "message.sent"]
              }
              """;

          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create("https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks"))
              .header("Authorization", "Bearer SEU_TOKEN")
              .header("Content-Type", "application/json")
              .POST(HttpRequest.BodyPublishers.ofString(body))
              .build();

          HttpResponse<String> response = HttpClient.newHttpClient()
              .send(request, HttpResponse.BodyHandlers.ofString());

          System.out.println(response.body());
      }
  }
  ```

  ```ruby Ruby theme={null}
  require "net/http"
  require "json"
  require "uri"

  uri = URI("https://api.zapsterapi.com/v1/wa/instances/PRIMEIRA_INSTANCIA/webhooks")

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer SEU_TOKEN"
  request["Content-Type"] = "application/json"
  request.body = {
    url: "https://sua-app.com/webhook",
    name: "Webhook de produção",
    events: ["message.received", "message.sent"]
  }.to_json

  response = http.request(request)
  data = JSON.parse(response.body)
  puts data["webhook_id"]
  ```
</CodeGroup>

**2. Demais instâncias: reutilize com `webhook_id`**

Use o `webhook_id` devolvido no passo anterior para associar o mesmo webhook a outra instância. Note que os eventos podem ser diferentes: cada instância assina o que faz sentido para ela.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks \
    -H "Authorization: Bearer SEU_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "webhook_id": "2nenz69l0xbf0m3uu9tfo",
      "events": ["message.received"]
    }'
  ```

  ```javascript JavaScript (client) theme={null}
  const response = await fetch(
    'https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks',
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer SEU_TOKEN',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        webhook_id: '2nenz69l0xbf0m3uu9tfo',
        events: ['message.received'],
      }),
    },
  );

  const data = await response.json();
  console.log(data);
  ```

  ```javascript JavaScript (server) theme={null}
  // Node.js 18+ (fetch nativo).
  const response = await fetch(
    'https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.ZAPSTER_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        webhook_id: '2nenz69l0xbf0m3uu9tfo',
        events: ['message.received'],
      }),
    },
  );

  console.log(await response.json());
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks');
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer SEU_TOKEN',
          'Content-Type: application/json',
      ],
      CURLOPT_POSTFIELDS => json_encode([
          'webhook_id' => '2nenz69l0xbf0m3uu9tfo',
          'events' => ['message.received'],
      ]),
  ]);

  $response = curl_exec($ch);
  curl_close($ch);

  echo $response;
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	body, _ := json.Marshal(map[string]any{
  		"webhook_id": "2nenz69l0xbf0m3uu9tfo",
  		"events":     []string{"message.received"},
  	})

  	req, _ := http.NewRequest(
  		http.MethodPost,
  		"https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks",
  		bytes.NewReader(body),
  	)
  	req.Header.Set("Authorization", "Bearer SEU_TOKEN")
  	req.Header.Set("Content-Type", "application/json")

  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	defer resp.Body.Close()

  	data, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(data))
  }
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks",
      headers={"Authorization": "Bearer SEU_TOKEN"},
      json={
          "webhook_id": "2nenz69l0xbf0m3uu9tfo",
          "events": ["message.received"],
      },
  )

  print(response.json())
  ```

  ```java Java theme={null}
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;

  public class ReuseWebhook {
      public static void main(String[] args) throws Exception {
          String body = """
              {
                "webhook_id": "2nenz69l0xbf0m3uu9tfo",
                "events": ["message.received"]
              }
              """;

          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create("https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks"))
              .header("Authorization", "Bearer SEU_TOKEN")
              .header("Content-Type", "application/json")
              .POST(HttpRequest.BodyPublishers.ofString(body))
              .build();

          HttpResponse<String> response = HttpClient.newHttpClient()
              .send(request, HttpResponse.BodyHandlers.ofString());

          System.out.println(response.body());
      }
  }
  ```

  ```ruby Ruby theme={null}
  require "net/http"
  require "json"
  require "uri"

  uri = URI("https://api.zapsterapi.com/v1/wa/instances/SEGUNDA_INSTANCIA/webhooks")

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer SEU_TOKEN"
  request["Content-Type"] = "application/json"
  request.body = {
    webhook_id: "2nenz69l0xbf0m3uu9tfo",
    events: ["message.received"]
  }.to_json

  puts http.request(request).body
  ```
</CodeGroup>

#### Editar e remover: o que muda

Como o webhook e a associação são coisas distintas, editar ou remover tem escopos diferentes. Vale conhecer cada operação antes de aplicar mudanças em produção.

| Operação              | Endpoint                                                             | Escopo    | Efeito                                               |
| --------------------- | -------------------------------------------------------------------- | --------- | ---------------------------------------------------- |
| Criar + associar novo | `POST /wa/instances/:id/webhooks` com `url`                          | Instância | Cria um webhook novo e associa à instância           |
| Reusar existente      | `POST /wa/instances/:id/webhooks` com `webhook_id`                   | Instância | Associa um webhook já existente a mais uma instância |
| Editar o webhook      | `PATCH /webhooks/:id` (url/name/enabled)                             | Conta     | Propaga para TODAS as instâncias associadas          |
| Editar a associação   | `PATCH /wa/instances/:id/webhooks/:whId` (events/enabled/test\_mode) | Instância | Muda só a assinatura daquela instância               |
| Desassociar           | `DELETE /wa/instances/:id/webhooks/:whId`                            | Instância | Só desliga daquela instância; segue ativo nas demais |
| Excluir               | `DELETE /webhooks/:id`                                               | Conta     | Remove de TODAS as instâncias                        |

<Warning>
  **Editar o webhook propaga para todo mundo.** Alterar a URL ou o nome pelo endpoint de conta (`PATCH /webhooks/:id`) afeta todas as instâncias associadas. Se você precisa de uma URL diferente para apenas uma instância, crie um webhook novo em vez de editar o existente.
</Warning>

<Note>
  **Desassociar não é o mesmo que excluir.** `DELETE /wa/instances/:id/webhooks/:whId` apenas desliga o webhook daquela instância; ele continua ativo nas outras e na sua conta. Para apagar o webhook de vez, use `DELETE /webhooks/:id`.
</Note>

#### Como diferenciar a origem no receptor

Cada instância entrega os eventos de forma independente, mesmo quando compartilham o mesmo webhook. Para saber de onde veio cada notificação, use os cabeçalhos HTTP:

* **`X-Instance-ID`**: identifica a instância que gerou o evento (a origem real).
* **`X-Webhook-ID`**: identifica o webhook compartilhado que entregou a notificação.

Como os eventos assinados podem ser diferentes em cada instância, o mesmo webhook pode receber `message.received` de uma instância e `message.sent` de outra. Sempre olhe o `X-Instance-ID` para rotear ou registrar o evento corretamente.

#### Quando compartilhar e quando separar

Compartilhar um webhook faz sentido quando:

* Todas as instâncias entregam para o mesmo sistema (um CRM, uma fila, um endpoint central).
* Você quer trocar a URL de destino em um só lugar no futuro.
* O processamento no receptor já usa o `X-Instance-ID` para separar as origens.

Webhooks distintos por instância fazem mais sentido quando:

* Cada instância pertence a um cliente ou produto diferente, com URL própria.
* Você precisa ligar ou desligar o destino de uma instância sem tocar nas outras.
* Ambientes separados (produção e homologação) não devem se misturar.

#### Boas práticas

* Guarde o `webhook_id` retornado na criação; é ele que você reutiliza nas próximas instâncias.
* Use o `name` do webhook para deixar claro o propósito (por exemplo, "CRM produção").
* No receptor, trate `X-Instance-ID` como a fonte da verdade sobre a origem do evento.
* Antes de editar a URL de um webhook compartilhado, confirme quais instâncias serão afetadas com o endpoint de [listagem de webhooks](/pt-BR/v1/api-reference/webhooks/list-webhooks).

#### Perguntas frequentes

<AccordionGroup>
  <Accordion title="Posso mudar a URL de uma instância sem afetar as outras?">
    Não pelo endpoint de edição do webhook, que é de conta e propaga para todas as instâncias associadas. Para uma URL exclusiva, crie um webhook novo enviando `url` na criação e associe apenas à instância desejada.
  </Accordion>

  <Accordion title="Se eu desassociar um webhook de uma instância, ele some da conta?">
    Não. `DELETE /wa/instances/:id/webhooks/:whId` só desliga o webhook daquela instância. Ele continua ativo nas demais e na sua conta. Para apagar de vez, use `DELETE /webhooks/:id`.
  </Accordion>

  <Accordion title="Instâncias que compartilham o webhook precisam assinar os mesmos eventos?">
    Não. Os eventos são definidos por instância, na associação. Uma pode assinar `message.received` e outra `message.sent`, mesmo apontando para o mesmo webhook.
  </Accordion>

  <Accordion title="Como sei qual instância enviou cada notificação?">
    Pelo cabeçalho `X-Instance-ID`, presente em toda notificação. O `X-Webhook-ID` indica o webhook que fez a entrega.
  </Accordion>

  <Accordion title="Posso enviar `url` e `webhook_id` na mesma requisição?">
    Envie apenas um dos dois. Se ambos forem informados, a Zapster prioriza o `webhook_id` e ignora a `url`.
  </Accordion>
</AccordionGroup>

Para os detalhes de cada endpoint, consulte a referência da API: [criar/associar webhook](/pt-BR/v1/api-reference/instance/create-webhook), [editar associação da instância](/pt-BR/v1/api-reference/instance/update-webhook), [desassociar da instância](/pt-BR/v1/api-reference/instance/delete-webhook), [editar o webhook](/pt-BR/v1/api-reference/webhooks/update-webhook) e [excluir o webhook](/pt-BR/v1/api-reference/webhooks/delete-webhook).

### Tratamento de Falhas e Retentativas

Em um cenário ideal, a aplicação receptora recebe e processa a solicitação do Webhook sem problemas. No entanto, falhas podem ocorrer devido a vários fatores, como indisponibilidade do servidor, problemas de rede, ou erros de processamento.

Para garantir que as notificações importantes não sejam perdidas, implementamos um mecanismo de **retentativa**. Se a aplicação receptora responder com um código de status HTTP maior que 400 (indicando um erro), a aplicação emissora tentará reenviar a notificação do Webhook até **5 vezes**.

#### Detalhes da Retentativa

* **Critério de Falha**: Qualquer resposta com código de status HTTP maior ou igual que 400.
* **Número de Retentativas**: Até 5 tentativas.
* **Intervalo entre Retentativas**: O intervalo entre cada retentativa aumenta progressivamente usando um fator de 2,5. Os intervalos em segundos são os seguintes:

  * **1ª Tentativa:** 2,5 segundos (`2.5^1`)
  * **2ª Tentativa:** \~6 segundos (`2.5^2`)
  * **3ª Tentativa:** \~15 segundos (`2.5^3`)
  * **4ª Tentativa:** \~39 segundos (`2.5^4`)
  * **5ª Tentativa:** \~97 segundos (`2.5^5`)

  Cada intervalo é calculado como `2,5^n`, onde `n` é o número da tentativa.

### Cabeçalhos HTTP Personalizados

Todas as notificações de webhook enviadas pela Zapster API incluem cabeçalhos HTTP personalizados que identificam a origem e o contexto do evento. Esses cabeçalhos são úteis para validação, logging e configuração de regras de firewall.

| Cabeçalho         | Tipo   | Descrição                          | Exemplo            | Presente em                    |
| ----------------- | ------ | ---------------------------------- | ------------------ | ------------------------------ |
| `X-Instance-ID`   | string | ID da instância que gerou o evento | `inst_abc123`      | Todas as notificações          |
| `X-Message-ID`    | string | ID único da notificação            | `msg_xyz789`       | Todas as notificações          |
| `X-Webhook-ID`    | string | ID do webhook registrado           | `whk_def456`       | Quando webhook está registrado |
| `X-Attempt-Count` | number | Número da tentativa (1-5)          | `1`                | Todas as notificações          |
| `User-Agent`      | string | Identificador do emissor           | `Zapsterapi/1.2.3` | Todas as notificações          |

### Validação e Allowlisting

Você pode utilizar os cabeçalhos HTTP personalizados para validar a origem das notificações recebidas. Como o webhook é entregue via `POST` no seu servidor, os exemplos abaixo mostram o endpoint receptor em cada linguagem (cURL e código de navegador não recebem webhooks, por isso não aparecem aqui):

<CodeGroup>
  ```javascript JavaScript (server) theme={null}
  // Node.js + Express
  app.post('/webhook', (req, res) => {
    const instanceId = req.headers['x-instance-id']
    const messageId = req.headers['x-message-id']
    const attemptCount = req.headers['x-attempt-count']

    console.log(`Notificação recebida da instância ${instanceId}`)
    console.log(`ID da mensagem: ${messageId}, tentativa: ${attemptCount}`)

    // Valide a origem antes de processar
    if (!instanceId) {
      return res.status(400).json({ error: 'Cabeçalho X-Instance-ID ausente' })
    }

    // Processe o evento
    const event = req.body
    console.log(`Evento: ${event.type}`, event.data)

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

  ```php PHP theme={null}
  <?php
  // Cabeçalhos chegam em $_SERVER com o prefixo HTTP_ e underscore no lugar do hífen
  $instanceId = $_SERVER['HTTP_X_INSTANCE_ID'] ?? null;
  $messageId = $_SERVER['HTTP_X_MESSAGE_ID'] ?? null;
  $attemptCount = $_SERVER['HTTP_X_ATTEMPT_COUNT'] ?? null;

  error_log("Notificação recebida da instância {$instanceId}");
  error_log("ID da mensagem: {$messageId}, tentativa: {$attemptCount}");

  // Valide a origem antes de processar
  if (!$instanceId) {
      http_response_code(400);
      echo json_encode(['error' => 'Cabeçalho X-Instance-ID ausente']);
      exit;
  }

  // Processe o evento
  $event = json_decode(file_get_contents('php://input'), true);
  error_log("Evento: {$event['type']}");

  http_response_code(200);
  echo json_encode(['received' => true]);
  ```

  ```go Go theme={null}
  package main

  import (
  	"encoding/json"
  	"log"
  	"net/http"
  )

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
  	instanceID := r.Header.Get("X-Instance-ID")
  	messageID := r.Header.Get("X-Message-ID")
  	attemptCount := r.Header.Get("X-Attempt-Count")

  	log.Printf("Notificação recebida da instância %s", instanceID)
  	log.Printf("ID da mensagem: %s, tentativa: %s", messageID, attemptCount)

  	// Valide a origem antes de processar
  	if instanceID == "" {
  		w.WriteHeader(http.StatusBadRequest)
  		json.NewEncoder(w).Encode(map[string]string{"error": "Cabeçalho X-Instance-ID ausente"})
  		return
  	}

  	// Processe o evento
  	var event map[string]any
  	json.NewDecoder(r.Body).Decode(&event)
  	log.Printf("Evento: %v", event["type"])

  	w.WriteHeader(http.StatusOK)
  	json.NewEncoder(w).Encode(map[string]bool{"received": true})
  }
  ```

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

  app = Flask(__name__)

  @app.post("/webhook")
  def webhook():
      instance_id = request.headers.get("X-Instance-ID")
      message_id = request.headers.get("X-Message-ID")
      attempt_count = request.headers.get("X-Attempt-Count")

      print(f"Notificação recebida da instância {instance_id}")
      print(f"ID da mensagem: {message_id}, tentativa: {attempt_count}")

      # Valide a origem antes de processar
      if not instance_id:
          return jsonify(error="Cabeçalho X-Instance-ID ausente"), 400

      # Processe o evento
      event = request.get_json()
      print(f"Evento: {event['type']}", event["data"])

      return jsonify(received=True), 200
  ```

  ```java Java theme={null}
  import org.springframework.http.ResponseEntity;
  import org.springframework.web.bind.annotation.*;
  import java.util.Map;

  @RestController
  public class WebhookController {

      @PostMapping("/webhook")
      public ResponseEntity<?> receive(
              @RequestHeader(value = "X-Instance-ID", required = false) String instanceId,
              @RequestHeader(value = "X-Message-ID", required = false) String messageId,
              @RequestHeader(value = "X-Attempt-Count", required = false) String attemptCount,
              @RequestBody Map<String, Object> event) {

          System.out.printf("Notificação recebida da instância %s%n", instanceId);
          System.out.printf("ID da mensagem: %s, tentativa: %s%n", messageId, attemptCount);

          // Valide a origem antes de processar
          if (instanceId == null) {
              return ResponseEntity.badRequest().body(Map.of("error", "Cabeçalho X-Instance-ID ausente"));
          }

          // Processe o evento
          System.out.println("Evento: " + event.get("type"));

          return ResponseEntity.ok(Map.of("received", true));
      }
  }
  ```

  ```ruby Ruby theme={null}
  require "sinatra"
  require "json"

  post "/webhook" do
    instance_id = request.env["HTTP_X_INSTANCE_ID"]
    message_id = request.env["HTTP_X_MESSAGE_ID"]
    attempt_count = request.env["HTTP_X_ATTEMPT_COUNT"]

    puts "Notificação recebida da instância #{instance_id}"
    puts "ID da mensagem: #{message_id}, tentativa: #{attempt_count}"

    # Valide a origem antes de processar
    halt 400, { error: "Cabeçalho X-Instance-ID ausente" }.to_json if instance_id.nil?

    # Processe o evento
    event = JSON.parse(request.body.read)
    puts "Evento: #{event['type']}"

    content_type :json
    { received: true }.to_json
  end
  ```
</CodeGroup>

**Allowlisting em WAF/Firewall:** Se você utiliza um Web Application Firewall (WAF) ou regras de firewall, configure-o para aceitar requisições `POST` que contenham os cabeçalhos `X-Instance-ID`, `X-Message-ID`, `X-Attempt-Count` e `User-Agent` com o prefixo `Zapsterapi/`.
