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

# Validação de Assinatura de Webhook

> Como garantir que os webhooks recebidos são autênticos e não foram alterados

## Objetivo

Toda requisição de webhook enviada pela NoxPay vem assinada. Validar essa assinatura garante que o webhook:

* **Não foi alterado** no caminho (integridade)
* **Foi realmente enviado pela NoxPay** (autenticidade)

<Warning>
  Valide **sempre** a assinatura **antes** de processar o conteúdo do webhook. Nunca confie em um payload não verificado.
</Warning>

## Como funciona

A NoxPay assina cada webhook usando um segredo compartilhado (o **Webhook Secret**). Do seu lado, você reproduz essa mesma assinatura localmente e compara com o valor recebido no header da requisição. Se baterem, o webhook é legítimo.

### Webhook Secret

* Chave secreta exclusiva por integração, gerada pela NoxPay
* Compartilhada com você de forma segura
* **Nunca** é enviada dentro do webhook — só você e a NoxPay a conhecem

<Warning>
  Nunca exponha o `webhook_secret` e nunca reutilize sua API Key como secret. Armazene-o de forma segura (variável de ambiente, cofre de segredos, etc.).
</Warning>

### Headers de assinatura

Existem dois métodos de assinatura em uso. Cada um chega em um header diferente:

| Método         | Header            | Status              |
| -------------- | ----------------- | ------------------- |
| HMAC-SHA256    | `X-Signature`     | Atual / recomendado |
| SHA256 simples | `X-Nox-Signature` | Legado              |

<Note>
  Ambos os headers podem chegar em integrações — inclusive novas. Por isso, sua implementação deve estar preparada para validar os dois. Priorize o `X-Signature` (HMAC) e trate o `X-Nox-Signature` como compatibilidade com o método antigo.
</Note>

***

## Método atual — HMAC-SHA256

Header de comparação: `X-Signature`

### Fórmula

```text theme={null}
Base64( HMAC_SHA256( key = webhook_secret, message = raw_body ) )
```

Onde:

* `webhook_secret` → o segredo compartilhado
* `raw_body` → o corpo da requisição **exatamente como recebido** (bytes brutos)
* encoding → UTF-8

### Passo a passo

<Steps>
  <Step title="Capture o raw body">
    Pegue o corpo da requisição exatamente como chegou, **sem parsear nem reserializar** o JSON.
  </Step>

  <Step title="Gere o HMAC-SHA256">
    Calcule o HMAC usando o `webhook_secret` como chave e o `raw_body` como mensagem.
  </Step>

  <Step title="Codifique em Base64">
    Converta o resultado binário do HMAC para uma string Base64.
  </Step>

  <Step title="Compare com segurança">
    Compare com o header `X-Signature` usando uma função de comparação em tempo constante (para evitar *timing attacks*).
  </Step>
</Steps>

### Exemplos de código

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib
  import base64

  def validate_hmac_signature(raw_body: bytes, webhook_secret: str, received_signature: str) -> bool:
      h = hmac.new(webhook_secret.encode("utf-8"), raw_body, hashlib.sha256).digest()
      expected = base64.b64encode(h).decode()
      # Comparação segura contra timing attacks
      return hmac.compare_digest(expected, received_signature)
  ```

  ```javascript Node.js theme={null}
  const crypto = require("crypto");

  function validateHmacSignature(rawBody, webhookSecret, receivedSignature) {
    const expected = crypto
      .createHmac("sha256", webhookSecret)
      .update(rawBody, "utf8")
      .digest("base64");

    // Comparação segura contra timing attacks
    const a = Buffer.from(expected);
    const b = Buffer.from(receivedSignature);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  }
  ```

  ```php PHP theme={null}
  <?php
  function validateHmacSignature(string $rawBody, string $webhookSecret, string $receivedSignature): bool {
      // hash_hmac com o 4º argumento "true" retorna bytes brutos
      $expected = base64_encode(hash_hmac('sha256', $rawBody, $webhookSecret, true));
      // hash_equals faz a comparação segura contra timing attacks
      return hash_equals($expected, $receivedSignature);
  }
  ```

  ```ruby Ruby theme={null}
  require 'openssl'
  require 'base64'

  def validate_hmac_signature(raw_body, webhook_secret, received_signature)
    digest   = OpenSSL::HMAC.digest('sha256', webhook_secret, raw_body)
    expected = Base64.strict_encode64(digest)
    # Comparação segura contra timing attacks
    OpenSSL.secure_compare(expected, received_signature)
  end
  ```

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

  import (
      "crypto/hmac"
      "crypto/sha256"
      "crypto/subtle"
      "encoding/base64"
  )

  func ValidateHMACSignature(rawBody, webhookSecret, receivedSignature string) bool {
      mac := hmac.New(sha256.New, []byte(webhookSecret))
      mac.Write([]byte(rawBody))
      expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
      // Comparação segura contra timing attacks
      return subtle.ConstantTimeCompare([]byte(expected), []byte(receivedSignature)) == 1
  }
  ```

  ```java Java theme={null}
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.nio.charset.StandardCharsets;
  import java.security.MessageDigest;
  import java.util.Base64;

  public class WebhookValidator {
      public static boolean validateHmacSignature(String rawBody, String webhookSecret, String receivedSignature) throws Exception {
          Mac mac = Mac.getInstance("HmacSHA256");
          mac.init(new SecretKeySpec(webhookSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
          byte[] hash = mac.doFinal(rawBody.getBytes(StandardCharsets.UTF_8));
          String expected = Base64.getEncoder().encodeToString(hash);
          // Comparação segura contra timing attacks
          return MessageDigest.isEqual(
              expected.getBytes(StandardCharsets.UTF_8),
              receivedSignature.getBytes(StandardCharsets.UTF_8));
      }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Security.Cryptography;
  using System.Text;

  public static class WebhookValidator
  {
      public static bool ValidateHmacSignature(string rawBody, string webhookSecret, string receivedSignature)
      {
          using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(webhookSecret));
          byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
          string expected = Convert.ToBase64String(hash);
          // Comparação segura contra timing attacks
          return CryptographicOperations.FixedTimeEquals(
              Encoding.UTF8.GetBytes(expected),
              Encoding.UTF8.GetBytes(receivedSignature));
      }
  }
  ```
</CodeGroup>

***

## Método legado — SHA256 simples

Header de comparação: `X-Nox-Signature`

<Warning>
  Este é o método **legado**. Ele ainda pode aparecer em qualquer integração, então mantenha o suporte a ele — mas prefira sempre o `X-Signature` (HMAC-SHA256) e não use este método como base de novas implementações.
</Warning>

### Fórmula

```text theme={null}
Base64( SHA256( webhook_secret + raw_body ) )
```

Aqui o segredo é simplesmente concatenado na frente do corpo, e o hash é feito em cima dessa junção.

### Exemplos de código

<CodeGroup>
  ```python Python theme={null}
  import hashlib
  import base64
  import hmac

  def validate_legacy_signature(raw_body: bytes, webhook_secret: str, received_signature: str) -> bool:
      content = webhook_secret.encode("utf-8") + raw_body
      hashed = hashlib.sha256(content).digest()
      expected = base64.b64encode(hashed).decode()
      return hmac.compare_digest(expected, received_signature)
  ```

  ```javascript Node.js theme={null}
  const crypto = require("crypto");

  function validateLegacySignature(rawBody, webhookSecret, receivedSignature) {
    const expected = crypto
      .createHash("sha256")
      .update(webhookSecret + rawBody, "utf8")
      .digest("base64");

    const a = Buffer.from(expected);
    const b = Buffer.from(receivedSignature);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  }
  ```

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

  import (
      "crypto/sha256"
      "crypto/subtle"
      "encoding/base64"
  )

  func ValidateLegacySignature(rawBody, webhookSecret, receivedSignature string) bool {
      sum := sha256.Sum256([]byte(webhookSecret + rawBody))
      expected := base64.StdEncoding.EncodeToString(sum[:])
      return subtle.ConstantTimeCompare([]byte(expected), []byte(receivedSignature)) == 1
  }
  ```
</CodeGroup>

<Accordion title="Por que o HMAC-SHA256 é preferível ao SHA256 simples?">
  O **SHA256 simples** é apenas uma função de hash genérica. No método legado, a segurança vem de concatenar o segredo na frente do corpo (`SHA256(webhook_secret + raw_body)`). O problema é que o SHA256 não foi desenhado para uso como código de autenticação: em certos cenários ele é vulnerável a um *length extension attack*, onde é possível calcular um novo hash válido sem conhecer o segredo completo.

  O **HMAC-SHA256** foi criado exatamente para esse fim. Ele aplica o hash em duas camadas, misturando a chave de formas diferentes em cada uma. Essa estrutura fecha a brecha do *length extension attack* e é matematicamente comprovada como segura para autenticação de mensagens — por isso é o padrão de mercado para assinar webhooks.

  Em resumo: o SHA256 simples é como colar uma senha na frente de um documento e embaralhar tudo junto. O HMAC usa a senha como uma chave que embaralha o conteúdo de um jeito estruturado e testado — muito mais difícil de burlar.
</Accordion>

***

## Pontos críticos

Valem para os **dois** métodos.

### 1. Use o raw body real (o mais importante)

A assinatura depende dos **bytes exatos** do corpo. Qualquer alteração quebra a validação, incluindo espaços, quebras de linha, ordem das chaves ou reserialização do JSON.

<CodeGroup>
  ```text Errado theme={null}
  Parsear o JSON e reformatar antes de validar
  ```

  ```text Correto theme={null}
  Usar o corpo bruto, exatamente como foi recebido
  ```
</CodeGroup>

<Tip>
  Webhook signature **não valida JSON — valida bytes exatos.** Capture o corpo cru antes de qualquer middleware que parseie a requisição (ex.: `express.raw()` no Express, ou o `await request.body()` cru no FastAPI).
</Tip>

### 2. Encoding

Use sempre **UTF-8**, em todas as etapas.

***

## Debug / Troubleshooting

Se a assinatura não bater, verifique nesta ordem:

* O **raw body** não foi alterado (causa nº 1)
* O **método correto** está sendo usado (HMAC para `X-Signature`, SHA256 legado para `X-Nox-Signature`)
* O **Webhook Secret** está correto
* O **encoding** é UTF-8
* O **header** correto está sendo lido

<Tip>
  Em ambiente seguro, logue a assinatura **esperada** vs a **recebida** lado a lado. A diferença geralmente aponta direto para a causa.
</Tip>
