Verificar a assinatura
Verifique a assinatura de toda entrega antes de processar. Sem isso, qualquer pessoa que descubra
a sua URL pode forjar um transaction.paid.
O algoritmo
Seção intitulada “O algoritmo”- Leia o corpo cru, byte a byte, antes de qualquer parse de JSON. Reserializar o JSON muda espaços e ordem e invalida a assinatura.
- Do cabeçalho
X-Auryon-Signature, pegue ot(segundos Unix) e todos osv1. - Monte
"<t>.<corpo cru>"e calculeHMAC-SHA256com a chave do webhook, em hexadecimal. - Aceite se algum
v1for igual, comparando em tempo constante. - Recuse se
testiver a mais de 5 minutos do seu relógio: é a proteção contra reenvio de uma entrega capturada (replay).
A chave do webhook (whsec_…) aparece uma única vez, quando o webhook é criado ou a chave é
rotacionada.
Node.js
Seção intitulada “Node.js”import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 300;
/** * Verifies the X-Auryon-Signature header of a webhook delivery. * rawBody: the request body exactly as received (string or Buffer), before any JSON parsing. */export function verifyAuryon(rawBody, header, secret, nowSeconds = Math.floor(Date.now() / 1000)) { let timestamp = null; const signatures = []; for (const part of String(header ?? '').split(',')) { const [key, value = ''] = part.trim().split('=', 2); if (key === 't' && /^\d+$/.test(value)) timestamp = Number(value); if (key === 'v1') signatures.push(value); } if (timestamp === null || Math.abs(nowSeconds - timestamp) > TOLERANCE_SECONDS) return false;
const expected = createHmac('sha256', secret) .update(`${timestamp}.`) .update(rawBody) .digest('hex'); return signatures.some( (s) => s.length === expected.length && timingSafeEqual(Buffer.from(s), Buffer.from(expected)), );}Com Express, leia o corpo cru só nesta rota:
app.post('/webhooks/auryon', express.raw({ type: 'application/json' }), (req, res) => { if (!verifyAuryon(req.body, req.get('X-Auryon-Signature'), process.env.AURYON_WEBHOOK_SECRET)) { return res.sendStatus(400); } const event = JSON.parse(req.body); // enfileire `event` e responda logo res.sendStatus(200);});<?php
const AURYON_TOLERANCE_SECONDS = 300;
/** * Verifies the X-Auryon-Signature header of a webhook delivery. * $rawBody: the request body exactly as received — file_get_contents('php://input'). */function verify_auryon(string $rawBody, string $header, string $secret, ?int $now = null): bool{ $now = $now ?? time(); $timestamp = null; $signatures = []; foreach (explode(',', $header) as $part) { [$key, $value] = array_pad(explode('=', trim($part), 2), 2, ''); if ($key === 't' && ctype_digit($value)) { $timestamp = (int) $value; } if ($key === 'v1') { $signatures[] = $value; } } if ($timestamp === null || abs($now - $timestamp) > AURYON_TOLERANCE_SECONDS) { return false; }
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret); foreach ($signatures as $signature) { if (hash_equals($expected, $signature)) { return true; } } return false;}$raw = file_get_contents('php://input');if (!verify_auryon($raw, $_SERVER['HTTP_X_AURYON_SIGNATURE'] ?? '', getenv('AURYON_WEBHOOK_SECRET'))) { http_response_code(400); exit;}$event = json_decode($raw, true);http_response_code(200);import hashlibimport hmacimport time
TOLERANCE_SECONDS = 300
def verify_auryon(raw_body: bytes, header: str, secret: str, now: int | None = None) -> bool: """Verifies the X-Auryon-Signature header of a webhook delivery.
raw_body: the request body exactly as received (bytes), before any JSON parsing. """ now = int(time.time()) if now is None else now timestamp = None signatures = [] for part in header.split(","): key, _, value = part.strip().partition("=") if key == "t" and value.isdigit(): timestamp = int(value) elif key == "v1": signatures.append(value) if timestamp is None or abs(now - timestamp) > TOLERANCE_SECONDS: return False
expected = hmac.new( secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256 ).hexdigest() return any(hmac.compare_digest(expected, s) for s in signatures)Com Flask:
@app.post("/webhooks/auryon")def auryon(): raw = request.get_data() if not verify_auryon(raw, request.headers.get("X-Auryon-Signature", ""), os.environ["AURYON_WEBHOOK_SECRET"]): abort(400) event = json.loads(raw) return "", 200Rotação da chave
Seção intitulada “Rotação da chave”Ao rotacionar no painel, durante 24 horas a Auryon assina com a chave nova e com a anterior:
o cabeçalho leva dois v1=, o da chave nova primeiro. Por isso a regra é aceitar qualquer v1
que bata. Troque a chave no seu servidor dentro das 24 horas, sem perder nenhum evento.
No formato legado não há essa janela: o token vai no corpo, e a partir da rotação só o novo é enviado.
Vetor de teste
Seção intitulada “Vetor de teste”Use estes valores para testar a sua implementação. Com now igual ao timestamp, a verificação do
body abaixo com secret deve dar verdadeiro; com qualquer byte do corpo alterado, ou com
now mais de 300 segundos depois, falso. Com signature_header_during_rotation, as duas chaves
(secret e previous_secret) devem passar.
{ "secret": "whsec_exemplo", "previous_secret": "whsec_anterior", "timestamp": 1781524800, "body": "{\"id\":\"evt_01JXEXEMPLO0000000000000000\",\"api_version\":\"2026-06-15\",\"type\":\"transaction.paid\",\"created\":1781524800,\"livemode\":true,\"data\":{\"object\":{\"id\":\"txn_exemplo_123\",\"status\":\"APROVADA\",\"payment_method\":\"PIX\",\"payment_method_detail\":null,\"amount\":\"19900\",\"shipping_amount\":\"0\",\"currency\":\"BRL\",\"installments\":1,\"identifier\":\"SEU-PEDIDO-123\",\"metadata\":{\"origem\":\"exemplo\"},\"offer_code\":\"ABC12345\",\"product\":{\"name\":\"Produto de exemplo\"},\"offer\":{\"name\":\"Oferta principal\"},\"customer\":{\"name\":\"Comprador de Exemplo\",\"email\":\"comprador@exemplo.test\"},\"created_at\":\"2026-06-15T12:00:00.000Z\",\"paid_at\":\"2026-06-15T12:00:00.000Z\"}}}", "signature_header": "t=1781524800,v1=18e77f9767e224aec6941555b5ce2ee0ab482b88e63b0e9b85375077f6977aea", "signature_header_during_rotation": "t=1781524800,v1=18e77f9767e224aec6941555b5ce2ee0ab482b88e63b0e9b85375077f6977aea,v1=71b3f6f314abc7cbc9d041d8903492f2ab6b0f62cbbb88faedefc460d816a1df"}Os três exemplos desta página são testados contra este vetor a cada geração da documentação.

