Webhook
A cada mudança de status de um pagamento, a CDX Pay envia o evento payment.status_changed ao
endpoint que você cadastrar. O webhook é um aviso: a fonte da verdade continua sendo
GET /v1/payments/{id}.
Cadastrar o endpoint: PUT /v1/webhook-endpoint
Permissão webhooks:write. A URL precisa ser https. Há um endpoint por estabelecimento.
curl -s -X PUT "$WP_API/v1/webhook-endpoint" \
-H "Authorization: Bearer $WP_KEY" \
-H 'Content-Type: application/json' \
-d '{ "url": "https://loja.example.com/hooks/pagamentos" }'
{ "url": "https://loja.example.com/hooks/pagamentos", "secret": "whsec_<segredo>",
"signature_header": "X-WebPayments-Signature", "updated_at": "2026-10-03T12:00:00Z" }
- O
secretaparece uma única vez, nesta resposta. Guarde-o no cofre do servidor. - Chamar
PUT /v1/webhook-endpointde novo troca a URL e gira o segredo: o anterior deixa de valer na hora. Atualize o seu servidor logo em seguida. GET /v1/webhook-endpointmostra a URL atual, sem o segredo.
O evento payment.status_changed
Cada evento chega como POST com corpo JSON e os cabeçalhos X-WebPayments-Event (o tipo do
evento) e X-WebPayments-Signature (a assinatura).
{
"event_id": "<uuid>",
"event_type": "payment.status_changed",
"payment_id": "<uuid>",
"external_reference": "pedido-1234",
"status": "captured",
"amount_cents": 15000,
"captured_cents": 15000,
"refunded_cents": 0,
"occurred_at": "2026-10-03T12:00:00Z"
}
| Campo do evento | O que é |
|---|---|
event_id |
id único do evento; use-o para descartar repetições |
event_type |
sempre payment.status_changed |
payment_id |
o id do pagamento |
external_reference |
o id do pedido que você enviou |
status |
o novo status (Status do pagamento) |
amount_cents, captured_cents, refunded_cents |
valores em centavos depois da mudança |
occurred_at |
quando a mudança aconteceu (UTC, ISO 8601) |
Verificar a assinatura
O cabeçalho tem a forma X-WebPayments-Signature: t=<unix>,v1=<hex>, em que t é o instante do
envio em segundos e v1 = HMAC-SHA256(secret, "<t>.<corpo cru>"), em hexadecimal.
- Leia o corpo cru, antes de qualquer conversão de JSON.
- Calcule o HMAC-SHA256 de
<t>.<corpo cru>com osecret. - Compare com
v1em tempo constante. - Recuse eventos com
ta mais de 5 minutos do seu relógio.
PHP:
$t = null;
$v1 = null;
foreach (explode(',', $request->header('X-WebPayments-Signature', '')) as $parte) {
[$k, $v] = array_pad(explode('=', $parte, 2), 2, null);
if ($k === 't') { $t = $v; }
if ($k === 'v1') { $v1 = $v; }
}
$esperado = hash_hmac('sha256', $t.'.'.$request->getContent(), $segredo);
$valido = $t !== null && $v1 !== null
&& hash_equals($esperado, $v1)
&& abs(time() - (int) $t) <= 300;
Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto'
function assinaturaValida(cabecalho, corpoCru, segredo) {
const partes = Object.fromEntries(cabecalho.split(',').map((p) => p.split('=', 2)))
if (!partes.t || !partes.v1) return false
if (Math.abs(Date.now() / 1000 - Number(partes.t)) > 300) return false
const esperado = createHmac('sha256', segredo).update(`${partes.t}.${corpoCru}`).digest('hex')
return esperado.length === partes.v1.length
&& timingSafeEqual(Buffer.from(esperado), Buffer.from(partes.v1))
}
Responder e reentrega
- Responda com qualquer 2xx em até 10 segundos e processe o evento depois, de forma assíncrona.
- Trate o evento de forma idempotente pelo
event_id: o mesmo evento pode chegar mais de uma vez. - Os eventos podem chegar fora de ordem. Use
occurred_atou, na dúvida, consulteGET /v1/payments/{id}. - Sem 2xx (erro, timeout ou falha de conexão), a CDX Pay tenta de novo depois de 1 min, 5 min, 30 min, 2 h e 12 h. Esgotadas as tentativas, o evento não é reenviado: consulte o pagamento para recuperar o status.