Account webhooks

The API tells your server when money comes in, goes out or fails. You create the subscription with your account’s accountId; omitting it returns 422 account_id_required. There is no “every account” subscription with this credential — that would read someone else’s movement.

Host: https://client.api.corpx.com. The envelope and the full event catalogue are in Webhooks (reference). This page is the first-day cut.

Create the subscription

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://your-domain.com/webhooks/account",
"accountId": "'"$ACCOUNT_ID"'",
"authType": "HMAC",
"secret": "'"$WEBHOOK_SECRET"'",
"eventTypes": [
"pix.in.completed",
"pix.out.completed",
"pix.out.failed",
"qrcode.paid"
]
}'

accountId is not editable afterwards. Change URL or events: PUT /v1/webhooks/{subscriptionId}. Change account: create another subscription.

authType: HMAC is recommended. The secret never comes back in responses (hmacSecretSet: true|false). Omitting secret on PUT keeps the key; sending a new value rotates it; authType: "NONE" deletes the key.

Event types: GET /v1/webhooks/events.

Useful first-day events

EventWhen
pix.in.completedPIX credited
qrcode.paidYour QR was paid (fires with pix.in.completed)
pix.out.completed / failed / timeoutPIX you sent
pix.refund.completedRefund you requested
ted.out.confirmed / failedTED you sent
ted.in.receivedTED credited
boleto.paidBoleto settled
transfer.internal.in / outInternal transfer

The catalogue has dozens more (MED, fees, accreditation). Subscribe only to what you will handle.

Verify HMAC

With authType: HMAC, each delivery has X-Signature: base64(HMAC_SHA256(secret, raw_body)). Use the raw bytes — re-serializing JSON changes field order and invalidates the signature.

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: the body must be a Buffer, not a parsed object.
app.post('/webhooks/account', 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);
// handle and reply 2xx quickly
res.sendStatus(200);
});

Reply 2xx when you receive the envelope. Heavy work goes on a queue. 4xx/5xx trigger another attempt.

Deliveries and retry

  • GET /v1/webhooks/{subscriptionId}/deliveries — attempts, newest first. Without fromDate, last 7 days.
  • GET /v1/webhooks/{subscriptionId}/deliveries/{deliveryId} — outbound envelope, when persisted.
  • POST .../deliveries/{deliveryId}/retry — redeliver to this subscription only.

Checklist

  • HTTPS on your endpoint
  • accountId at creation
  • HMAC checked against the raw body
  • Fast 2xx; async processing
  • Idempotency on your side: the same eventId may arrive again