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

# Validando a assinatura

> Confira o X-Bukio-Signature de todo webhook antes de confiar nele, com exemplos em Node.js, PHP e Python.

Todo envio leva o cabeçalho:

```http theme={null}
X-Bukio-Signature: t=1791471852,v1=5f1d2e3c4b5a69788796a5b4c3d2e1f0a1b2c3d4e5f60718293a4b5c6d7e8f90
```

* `t` é o horário do envio (Unix, segundos).
* `v1` é o HMAC-SHA256, em hexadecimal, da string `"<t>.<corpo>"`, com o seu segredo (`whsec_...`, o da URL ou o da conta).

## Como validar

**1. Pegue o corpo cru**

Os bytes exatos da requisição, antes de qualquer parse de JSON. Reformatar o JSON muda a assinatura.

**2. Separe t e v1**

Divida o cabeçalho por vírgula e cada parte por `=`.

**3. Recuse horário velho**

Se `t` estiver a mais de **5 minutos** do relógio do seu servidor, recuse (proteção contra repetição).

**4. Calcule e compare**

`HMAC-SHA256(segredo, t + "." + corpo)` em hex. Compare com `v1` em **tempo constante**.

**5. Ignore repetidos**

Guarde o `id` do evento (`evt_...`) e descarte se já tiver sido processado.

## Exemplos

> [tab] Node.js
>
> ```typescript theme={null}
> import { createHmac, timingSafeEqual } from "node:crypto";
> import express from "express";
>
> const SECRET = process.env.BUKIO_WEBHOOK_SECRET!; // whsec_...
>
> function verify(header: string | undefined, rawBody: string) {
>   if (!header) return false;
>   const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
>   const t = Number(parts.t);
>   if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
>   const expected = createHmac("sha256", SECRET).update(`${t}.${rawBody}`).digest("hex");
>   const given = String(parts.v1 ?? "");
>   return expected.length === given.length && timingSafeEqual(Buffer.from(expected), Buffer.from(given));
> }
>
> const app = express();
> // Corpo cru: sem express.json() nesta rota.
> app.post("/webhooks/bukio", express.raw({ type: "application/json" }), (req, res) => {
>   const raw = req.body.toString("utf8");
>   if (!verify(req.header("x-bukio-signature"), raw)) return res.status(400).end();
>   const event = JSON.parse(raw);
>   // guarde event.id e ignore repetidos; responda antes do trabalho pesado
>   res.status(200).end();
>   if (event.type === "payment.paid") liberarPedido(event.data.id, event.data.amount);
> });
> ```

> [tab] PHP
>
> ```php theme={null}
> <?php
> $secret = getenv('BUKIO_WEBHOOK_SECRET'); // whsec_...
> $raw = file_get_contents('php://input');
> $header = $_SERVER['HTTP_X_BUKIO_SIGNATURE'] ?? '';
>
> $parts = [];
> foreach (explode(',', $header) as $pair) {
>     [$k, $v] = array_pad(explode('=', $pair, 2), 2, '');
>     $parts[$k] = $v;
> }
> $t = (int) ($parts['t'] ?? 0);
> if ($t === 0 || abs(time() - $t) > 300) { http_response_code(400); exit; }
>
> $expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
> if (!hash_equals($expected, $parts['v1'] ?? '')) { http_response_code(400); exit; }
>
> $event = json_decode($raw, true);
> http_response_code(200); // responda antes do trabalho pesado
> if ($event['type'] === 'payment.paid') {
>     // liberar o pedido $event['data']['id'] no valor $event['data']['amount'] (centavos)
> }
> ```

> [tab] Python
>
> ```python theme={null}
> import hmac, hashlib, json, os, time
> from flask import Flask, request, abort
>
> SECRET = os.environ["BUKIO_WEBHOOK_SECRET"].encode()  # whsec_...
> app = Flask(__name__)
>
> def verify(header: str, raw: bytes) -> bool:
>     parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
>     try:
>         t = int(parts["t"])
>     except (KeyError, ValueError):
>         return False
>     if abs(time.time() - t) > 300:
>         return False
>     expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
>     return hmac.compare_digest(expected, parts.get("v1", ""))
>
> @app.post("/webhooks/bukio")
> def bukio():
>     raw = request.get_data()  # corpo cru
>     if not verify(request.headers.get("X-Bukio-Signature", ""), raw):
>         abort(400)
>     event = json.loads(raw)
>     # guarde event["id"] e ignore repetidos; enfileire o trabalho pesado
>     return "", 200
> ```

## Erros comuns

| Sintoma | Causa provável |
| - | - |
| Assinatura nunca bate | o framework fez parse do JSON e você reserializou; use o corpo cru |
| Bate no teste e falha em produção | segredo errado: a URL tem segredo próprio e você usou o da conta (ou o contrário) |
| Falha de vez em quando | relógio do servidor atrasado ou adiantado mais de 5 minutos; sincronize com NTP |
| Pedido liberado duas vezes | o mesmo `evt_` chegou de novo (reenvio ou segunda URL); guarde o id |

## Trocar o segredo

Em Integrações e API › Webhooks › **Gerar novo segredo**. O antigo para de valer na hora, então atualize o seu servidor primeiro e gere o novo em seguida; os envios na fila já saem assinados com o novo.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.