Documentação · Guias
Webhooks: receber e verificar
O Urdume envia um POST com JSON para o seu endereço sempre que acontece algo que subscreveu. Cada envio é assinado com o segredo do endereço.
1. Configurar
- Em Programadores › Webhooks, crie um endereço HTTPS e escolha os eventos (ou todos).
- Guarde o segredo (whsec_…) no servidor, por exemplo em URDUME_WEBHOOK_SECRET. Só é mostrado quando o pede.
- Use "Enviar evento de teste" para experimentar. Os endereços de teste recebem só os eventos de teste, e os de produção só os de produção.
2. O que chega
Pedido
POST /webhooks/urdume HTTP/1.1
Content-Type: application/json; charset=utf-8
Urdume-Signature: t=1790086400,v1=5f1c…
Urdume-Event-Id: 01JA4…
Urdume-Event-Type: passport_session.completed
{
"id": "01JA4…",
"object": "event",
"type": "passport_session.completed",
"api_version": "v1",
"created": 1790086400,
"livemode": false,
"data": {
"object": { "id": "01JA2…", "object": "passport_session", "status": "completed", ... }
}
}
"data.object" é o objeto em causa, tal como a API o devolve. A lista completa de eventos está na referência da API.
3. Verificar a assinatura
Urdume-Signature tem um timestamp (t) e a assinatura (v1) = HMAC-SHA256 do texto "t.corpo" com o segredo. Calcule-a sobre o corpo em bruto, compare em tempo constante e recuse timestamps com mais de 5 minutos.
Verificação
PHP
<?php
$body = file_get_contents('php://input'); // o corpo exatamente como chegou
$header = $_SERVER['HTTP_URDUME_SIGNATURE'] ?? '';
$secret = getenv('URDUME_WEBHOOK_SECRET'); // whsec_… (o valor completo)
$parts = [];
foreach (explode(',', $header) as $piece) {
[$key, $value] = array_pad(explode('=', trim($piece), 2), 2, '');
$parts[$key][] = $value;
}
$timestamp = (int) ($parts['t'][0] ?? 0);
$expected = hash_hmac('sha256', $timestamp . '.' . $body, $secret);
$valid = abs(time() - $timestamp) <= 300
&& array_filter($parts['v1'] ?? [], static fn ($s) => hash_equals($expected, $s)) !== [];
if (!$valid) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
if ($event['type'] === 'passport_session.completed') {
$session = $event['data']['object'];
// ex.: marcar o produto $session['metadata']['produto_id'] como "com passaporte"
}
http_response_code(200);
Node
const crypto = require('crypto');
const express = require('express');
// O corpo tem de chegar em bruto (Buffer) para a assinatura bater certo.
app.post('/webhooks/urdume', express.raw({ type: 'application/json' }), (req, res) => {
const parts = {};
for (const piece of (req.get('Urdume-Signature') || '').split(',')) {
const [key, value] = piece.trim().split('=');
(parts[key] ||= []).push(value);
}
const timestamp = Number(parts.t?.[0] || 0);
const expected = crypto
.createHmac('sha256', process.env.URDUME_WEBHOOK_SECRET)
.update(`${timestamp}.`)
.update(req.body)
.digest('hex');
const valid = Math.abs(Date.now() / 1000 - timestamp) <= 300 &&
(parts.v1 || []).some((s) => s.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
if (!valid) return res.sendStatus(400);
const event = JSON.parse(req.body);
if (event.type === 'passport_session.completed') {
// ...
}
res.sendStatus(200);
});
Python
import hashlib, hmac, json, os, time
from flask import abort, request
@app.post("/webhooks/urdume")
def urdume_webhook():
body = request.get_data() # bytes, exatamente como chegaram
parts = {}
for piece in request.headers.get("Urdume-Signature", "").split(","):
key, _, value = piece.strip().partition("=")
parts.setdefault(key, []).append(value)
timestamp = int(parts.get("t", ["0"])[0] or 0)
expected = hmac.new(
os.environ["URDUME_WEBHOOK_SECRET"].encode(), f"{timestamp}.".encode() + body, hashlib.sha256
).hexdigest()
valid = abs(time.time() - timestamp) <= 300 and any(
hmac.compare_digest(expected, s) for s in parts.get("v1", [])
)
if not valid:
abort(400)
event = json.loads(body)
if event["type"] == "passport_session.completed":
session = event["data"]["object"]
# ...
return "", 200
Boas práticas
- Responda 2xx depressa (em menos de 10 segundos) e faça o trabalho pesado depois. Outra resposta conta como falha.
- O mesmo evento pode chegar mais do que uma vez: use Urdume-Event-Id para não o tratar duas vezes.
- As falhas repetem-se ao fim de 1 min, 5 min, 30 min, 2 h, 5 h, 10 h e 24 h. Se 10 entregas seguidas esgotarem as tentativas, o endereço é desligado (volta a ligar-se no painel, onde também pode reenviar entregas).
- Não siga redirecionamentos: o Urdume não os segue e só envia para endereços HTTPS públicos.