Webhooks
Assinatura
Headers de cada entrega e como verificar a assinatura HMAC-SHA256 antes de confiar no payload.
Cada entrega de webhook é assinada com HMAC-SHA256, usando o segredo do webhook
(whsec_…) gerado na criação do endpoint. Sempre verifique a assinatura antes de
processar o payload.
Headers da entrega
| Header | Descrição |
|---|---|
X-HSDesk-Event | Tipo do evento (ex.: ticket.resposta_publica). |
X-HSDesk-Delivery | UUID da entrega; use para idempotência (estável entre retries). |
X-HSDesk-Timestamp | Unix timestamp usado na assinatura. |
X-HSDesk-Signature | sha256=<hmac_hex>. |
Como a assinatura é calculada
assinatura = "sha256=" + HMAC_SHA256(segredo, "<timestamp>.<corpo_bruto>")Use o corpo cru (raw body)
Calcule o HMAC sobre o corpo exatamente como recebido: não re-serialize o JSON. Um
re-JSON.stringify reordena chaves/espaços e quebra a assinatura.
Verificação
const crypto = require('crypto');
const esperado = 'sha256=' + crypto
.createHmac('sha256', SEGREDO_WEBHOOK)
.update(`${req.headers['x-hsdesk-timestamp']}.${rawBody}`)
.digest('hex');
const ok = crypto.timingSafeEqual(
Buffer.from(esperado),
Buffer.from(req.headers['x-hsdesk-signature']),
);
if (!ok) return res.status(400).end();$payload = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_HSDESK_TIMESTAMP'];
$assinatura = $_SERVER['HTTP_X_HSDESK_SIGNATURE']; // "sha256=..."
$esperado = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $payload, $SEGREDO_WEBHOOK);
if (! hash_equals($esperado, $assinatura)) {
http_response_code(400);
exit;
}import hashlib, hmac
payload = request.get_data() # bytes crus
timestamp = request.headers['X-HSDesk-Timestamp']
assinatura = request.headers['X-HSDesk-Signature'] # "sha256=..."
esperado = 'sha256=' + hmac.new(
SEGREDO_WEBHOOK.encode(),
f"{timestamp}.".encode() + payload,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(esperado, assinatura):
abort(400)Proteção contra replay
O X-HSDesk-Timestamp permite rejeitar entregas antigas. Recomendado: descartar entregas
cujo timestamp seja mais velho que a tolerância de relógio (padrão 300 segundos).
const idade = Math.floor(Date.now() / 1000) - Number(timestamp);
if (idade > 300) return res.status(400).end(); // possível replayCombine com a deduplicação por X-HSDesk-Delivery: se já processou aquele UUID, ignore.