# Verificar assinaturas (https://docs.pulsarml.com/pt/webhooks/signatures)

Confirme que cada pedido de webhook vem realmente do Pulsar e não foi alterado pelo caminho.



O seu endpoint é um URL público, por isso qualquer pessoa lhe pode enviar um pedido. Cada notificação do Pulsar inclui um cabeçalho `X-Pulsar-Signature`, um hash do corpo do pedido calculado com o seu **segredo de assinatura**. O segredo só é conhecido pelo Pulsar e por si, por isso uma assinatura que coincide prova que o pedido veio do Pulsar e que o corpo não foi alterado.

A assinatura é o **HMAC-SHA256** do **corpo em bruto do pedido**, com o seu segredo de assinatura como chave, escrito em **hexadecimal em minúsculas**.

<Callout type="warn" title="Use o segredo exatamente como o Pulsar lho fornece">
  O segredo de assinatura é uma cadeia de 32 caracteres. Use o seu texto (bytes UTF-8) como chave do HMAC, tal como está. &#x2A;*Não o descodifique de Base64 nem de hexadecimal.** A descodificação costuma funcionar sem erros, mas produz a chave errada, e então todas as verificações de assinatura falham.
</Callout>

## Como verificar um pedido [#how-to-verify-a-request]

<Steps>
  <Step>
    ### Leia o corpo em bruto [#read-the-raw-body]

    Calcule a assinatura sobre os **bytes exatos** que o Pulsar enviou, antes de qualquer parsing do JSON. Fazer o parsing e voltar a serializar o JSON pode alterar os espaços ou a ordem das chaves, o que altera o hash. A maioria das frameworks fornece o corpo em bruto separadamente; consulte os exemplos abaixo.
  </Step>

  <Step>
    ### Calcule a assinatura esperada [#compute-the-expected-signature]

    Calcule o HMAC-SHA256 do corpo em bruto com o seu segredo de assinatura como chave, e codifique o resultado em hexadecimal.
  </Step>

  <Step>
    ### Compare-a com o cabeçalho [#compare-it-with-the-header]

    Compare o seu resultado com o cabeçalho `X-Pulsar-Signature` através de uma comparação de **tempo constante** (`hmac.compare_digest` em Python, `crypto.timingSafeEqual` em Node.js). Um `==` simples pode revelar, pelo tempo de resposta, que parte de uma assinatura adivinhada estava certa.
  </Step>

  <Step>
    ### Aceite ou rejeite [#accept-or-reject]

    Se as assinaturas coincidirem, processe o evento e devolva `2XX`. Se o cabeçalho faltar ou não coincidir, devolva `401` e ignore o pedido.
  </Step>
</Steps>

<Callout title="Teste primeiro a sua verificação com um ping">
  O Pulsar não volta a tentar após uma resposta `4XX` (consulte [Novas tentativas](/pt/webhooks#retries)). Por isso, se o seu segredo estiver mal configurado, os eventos reais são rejeitados e ficam por entregar até o Pulsar os reenviar. Peça um [ping](/pt/webhooks#ping-events) e confirme que o seu endpoint devolve `2XX` antes de depender dele.
</Callout>

## Código de exemplo [#sample-code]

Ambos os exemplos leem o segredo de uma variável de ambiente `PULSAR_WEBHOOK_SECRET`.

<Tabs items="['Python (Flask)', 'Node.js (Express)']">
  <Tab value="Python (Flask)">
    ```python
    import hashlib
    import hmac
    import os

    from flask import Flask, request

    app = Flask(__name__)
    SECRET = os.environ["PULSAR_WEBHOOK_SECRET"].encode("utf-8")


    @app.post("/webhooks/pulsar")
    def pulsar_webhook():
        signature = request.headers.get("X-Pulsar-Signature", "")

        # Os bytes exatos que o Pulsar enviou, antes de fazer o parsing do JSON.
        body = request.get_data()
        expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()

        if not hmac.compare_digest(expected, signature):
            return "", 401

        notification = request.get_json()
        if notification["eventType"] == "Ping":
            return "", 200

        # Coloque o trabalho em fila e responda de imediato; consulte "Responda com um 2XX, rapidamente".
        enqueue(notification)
        return "", 202
    ```
  </Tab>

  <Tab value="Node.js (Express)">
    ```js
    const crypto = require('node:crypto');
    const express = require('express');

    const app = express();
    const SECRET = process.env.PULSAR_WEBHOOK_SECRET;

    // express.raw conserva os bytes exatos que o Pulsar enviou. Não use express.json()
    // nesta rota: faz o parsing do corpo antes de poder verificar a assinatura.
    app.post('/webhooks/pulsar', express.raw({ type: 'application/json' }), (req, res) => {
      const signature = Buffer.from(req.get('X-Pulsar-Signature') ?? '');
      const expected = Buffer.from(
        crypto.createHmac('sha256', SECRET).update(req.body).digest('hex'),
      );

      if (signature.length !== expected.length || !crypto.timingSafeEqual(signature, expected)) {
        return res.sendStatus(401);
      }

      const notification = JSON.parse(req.body.toString('utf8'));
      if (notification.eventType === 'Ping') return res.sendStatus(200);

      // Coloque o trabalho em fila e responda de imediato; consulte "Responda com um 2XX, rapidamente".
      enqueue(notification);
      res.sendStatus(202);
    });
    ```
  </Tab>
</Tabs>

## Pedidos repetidos [#replayed-requests]

A assinatura cobre apenas o corpo, por isso qualquer pessoa que obtenha uma cópia de um pedido, por exemplo de um log, poderia voltar a enviá-lo sem alterações e continuaria a passar a verificação. Proteja-se da mesma forma que [trata os duplicados](/pt/webhooks#duplicates-and-ordering): registe cada `eventId` que processar e ignore os que já tiver visto.

Não rejeite pedidos só porque `eventTimestamp` é antigo. As novas tentativas e os reenvios voltam a enviar propositadamente o evento original, com o seu carimbo de data/hora original, possivelmente muito mais tarde.
