Webhooks
Avisos por POST quando algo muda, com assinatura HMAC, retentativas e o evento de teste.
A Confido avisa o seu servidor com um POST JSON a cada evento: venda paga, recusada, estornada, subconta aprovada e
outros. Use os webhooks para liberar pedidos, em vez de consultar o status de tempos em tempos.
Cadastrar
curl -X POST https://api.confidoscm.com/api/v1/webhook-endpoints \
-H "X-API-Key: $CONFIDO_API_KEY" -H "Content-Type: application/json" \
-d '{
"url": "https://loja.exemplo.com/webhooks/confido",
"enabledEvents": ["payment.paid", "payment.declined", "payment.refunded"],
"description": "Loja principal"
}'
- A resposta traz o
secretde assinatura uma vez só. Guarde no seu servidor. - A URL precisa ser pública e em
httpsna porta 443 (endereços internos,localhoste IPs privados são recusados). storeIdlimita aos eventos de uma loja;connectScope(childrenousubtree) inclui os das subcontas.- Também dá para cadastrar no painel: Configurações › Integrações › Webhooks.
- Escopos:
webhooks:readewebhooks:write.
Eventos
| Grupo | Eventos |
|---|---|
| Pagamentos | payment.processing, payment.pending, payment.authorized, payment.requires_action, payment.paid, payment.declined, payment.failed, payment.expired, payment.cancelled, payment.refunded, payment.partially_refunded, payment.chargeback |
| Estornos e disputas | refund.completed, dispute.opened, dispute.accepted, dispute.won, dispute.lost, dispute.closed, dispute.evidence_deadline_approaching |
| Subcontas | account.created, account.updated, account.verification_updated, account.approved, account.rejected |
| Transferências | transfer.created |
| Assinaturas | subscription.created, subscription.renewed, subscription.payment_failed, subscription.cancel_scheduled, subscription.cancelled, subscription.completed |
| Teste | webhook.test |
O que chega
{
"id": "evt_…",
"object": "event",
"type": "payment.paid",
"api_version": "2026-01-01",
"livemode": true,
"created_at": "2026-11-03T18:02:09.000Z",
"occurred_at": "2026-11-03T18:02:09.000Z",
"data": {
"id": "pay_9f2c4e1a7b3d",
"object": "payment",
"status": "paid",
"payment_method": "pix",
"currency": "BRL",
"amount_cents": 15000,
"net_amount_cents": 14801,
"installments": 1,
"description": "Pedido 42",
"metadata": null,
"idempotency_key": "pedido-42",
"customer_id": "…",
"paid_at": "2026-11-03T18:02:09.000Z",
"livemode": true
},
"previous_data": null
}
Nos eventos de pagamento, o data é o pagamento completo, em snake_case (amount_cents, payment_method,
net_amount_cents, paid_at…), com o id igual ao publicId da API (pay_…). O previous_data, quando vem preenchido, traz os valores anteriores.
Com os cabeçalhos:
| Cabeçalho | O que é |
|---|---|
X-Webhook-Id |
Id estável da entrega: use para ignorar repetições. |
X-Webhook-Event-Id, X-Webhook-Event-Type |
O evento e o tipo. |
X-Webhook-Timestamp |
Segundos desde 1970 (UTC), usado na assinatura. |
X-Webhook-Signature |
v1=<HMAC-SHA256 em hex>. |
X-Webhook-Attempt |
O número da tentativa (1 a 4). |
O campo account aparece nos eventos de subconta. Na sandbox, livemode vem sempre false.
Conferir a assinatura
Calcule o HMAC-SHA256 de "<timestamp>.<corpo cru>" com o seu secret e compare com o v1= do cabeçalho, antes
de interpretar o JSON. Recuse timestamps com mais de 5 minutos de diferença. Nas 24 horas depois de trocar o segredo,
o cabeçalho traz duas assinaturas separadas por vírgula: aceite qualquer uma.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function webhookValido(corpoCru, headers, segredo) {
const ts = Number(headers['x-webhook-timestamp']);
if (!Number.isInteger(ts) || Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) return false;
const esperado = Buffer.from(
'v1=' + createHmac('sha256', segredo).update(`${ts}.${corpoCru}`, 'utf8').digest('hex'),
);
return String(headers['x-webhook-signature'] ?? '')
.split(',')
.some((v) => {
const recebido = Buffer.from(v.trim());
return recebido.length === esperado.length && timingSafeEqual(recebido, esperado);
});
}
import hmac, hashlib, time
def webhook_valido(corpo_cru: bytes, headers: dict, segredo: str) -> bool:
ts = int(headers.get("x-webhook-timestamp", "0"))
if abs(int(time.time()) - ts) > 300:
return False
esperado = "v1=" + hmac.new(segredo.encode(), f"{ts}.".encode() + corpo_cru, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(esperado, v.strip()) for v in headers.get("x-webhook-signature", "").split(","))
Autorize pela assinatura, não pelo IP: a Confido não tem uma lista fixa de IPs de saída.
Responder e repetir
- Responda
2xxem até 5 segundos e processe depois, numa fila. Redirecionamentos não são seguidos. - A entrega é pelo menos uma vez e sem ordem garantida: ignore repetições pelo
X-Webhook-Ide useoccurred_atpara saber qual estado é o mais novo. - Sem
2xx, a Confido tenta de novo, até 4 vezes em até 72 horas, com espera crescente (até 8 h, 16 h e 24 h, com sorteio).Retry-Afterna sua resposta adia a próxima tentativa. Depois disso, a entrega fica parada e pode ser reenviada pelo painel (Integrações › Logs › Reenviar).
Testar e trocar o segredo
| Rota | O que faz |
|---|---|
POST /api/v1/webhook-endpoints/{id}/test |
Manda um webhook.test agora e devolve o resultado (succeeded ou failed, status e tempo). |
POST /api/v1/webhook-endpoints/{id}/rotate-secret |
Gera um segredo novo; o anterior ainda assina por 24 horas. |
PATCH e DELETE /api/v1/webhook-endpoints/{id} |
Edita ou exclui (as entregas pendentes são canceladas). |