Webhooks da conta

A API avisa o seu servidor quando o dinheiro entra, sai ou falha. Você cria a assinatura com o accountId da sua conta; omitir devolve 422 account_id_required. Não existe assinatura “de todas as contas” com esta credencial — isso seria ler o movimento de outra pessoa.

Host: https://client.api.corpx.com. O envelope e o catálogo completo de tipos estão em Webhooks (referência). Aqui está o recorte que importa no primeiro dia.

Criar a assinatura

curl -X POST "https://client.api.corpx.com/v1/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: $TENANT_ID" \
-H "Content-Type: application/json" \
-H "X-Request-Timestamp: $TS" \
-H "X-Content-SHA256: $SHA" \
-H "X-Request-Signature: $SIG" \
-d '{
"url": "https://seu-dominio.com/webhooks/conta",
"accountId": "'"$ACCOUNT_ID"'",
"authType": "HMAC",
"secret": "'"$WEBHOOK_SECRET"'",
"eventTypes": [
"pix.in.completed",
"pix.out.completed",
"pix.out.failed",
"qrcode.paid"
]
}'

accountId não se edita depois. Trocar de URL ou de eventos: PUT /v1/webhooks/{subscriptionId}. Trocar de conta: crie outra assinatura.

authType: HMAC é o recomendado. O segredo nunca volta nas respostas (hmacSecretSet: true|false). Omitir secret no PUT mantém a chave; enviar um valor novo rotaciona; authType: "NONE" apaga a chave.

Lista de tipos: GET /v1/webhooks/events.

Eventos úteis no primeiro dia

EventoQuando
pix.in.completedPIX creditado
qrcode.paidQR seu foi pago (sai junto com pix.in.completed)
pix.out.completed / failed / timeoutPIX que você enviou
pix.refund.completedDevolução que você pediu
ted.out.confirmed / failedTED que você enviou
ted.in.receivedTED creditado
boleto.paidBoleto liquidado
transfer.internal.in / outTransferência interna

O catálogo tem dezenas de outros (MED, tarifas, accreditation). Só assine o que você vai tratar.

Validar o HMAC

Com authType: HMAC, cada entrega traz X-Signature: base64(HMAC_SHA256(secret, corpo_bruto)). Use os bytes brutos — re-serializar o JSON muda a ordem dos campos e invalida a assinatura.

const crypto = require('node:crypto');
function verifySignature(secret, rawBody, signatureHeader) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader || '', 'utf8'));
}
// Express: o body precisa ser Buffer, não objeto parseado.
app.post('/webhooks/conta', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifySignature(process.env.WEBHOOK_SECRET, req.body, req.headers['x-signature'])) {
return res.status(403).send('invalid signature');
}
const event = JSON.parse(req.body);
// processe e responda 2xx rápido
res.sendStatus(200);
});

Responda 2xx quando receber o envelope. Trabalho pesado fica na fila. 4xx/5xx disparam nova tentativa.

Entregas e reenvio

  • GET /v1/webhooks/{subscriptionId}/deliveries — tentativas, mais recentes primeiro. Sem fromDate, últimos 7 dias.
  • GET /v1/webhooks/{subscriptionId}/deliveries/{deliveryId} — envelope enviado, quando persistido.
  • POST .../deliveries/{deliveryId}/retry — reentrega só nesta assinatura.

Checklist

  • HTTPS no seu endpoint
  • accountId na criação
  • HMAC conferido com o corpo bruto
  • 2xx rápido; processamento assíncrono
  • Idempotência no seu lado: o mesmo eventId pode chegar de novo