Webhooks

Webhooks are notifications sent by our API to your registered URL whenever a relevant event occurs (e.g., a PIX is received or a payment is completed).

Webhook Configuration

How you create and maintain subscriptions depends on your product:

You have two equivalent, always-in-sync paths:

  • APIPOST /v1/webhooks and the other routes described on this page. This is how you automate the setup alongside the rest of your deployment.
  • Integrator Portal — besides creating and editing subscriptions, this is where you run day-to-day operations: resend failed deliveries, debug errors (HTTP status and body returned by your endpoint), browse the delivery history of every event and rotate the HMAC key without interrupting the flow.

Use the API to provision and the Portal to operate and investigate.

List Available Events

curl -X GET "https://tenant.api.corpx.com/v1/webhooks/events" \
-H "Authorization: Bearer YOUR_TOKEN"

Response (array of { event, description }; illustrative excerpt):

[
{ "event": "pix.in.completed", "description": "PIX received successfully" },
{ "event": "pix.out.completed", "description": "PIX sent successfully" },
{ "event": "accreditation.pf.created", "description": "PF accreditation created (async confirmation of POST)" },
{ "event": "accreditation.pj.created", "description": "PJ accreditation created (async confirmation of POST)" },
{ "event": "accreditation.biometry.link.created", "description": "Facial capture link issued (Unico flow; per person)" },
{ "event": "accreditation.acceptance.link.created", "description": "Terms acceptance link issued (per person; only in flows enabled on request)" },
{ "event": "accreditation.consent.link.created", "description": "Authorization link issued — CPF already has an account (per person)" },
{ "event": "accreditation.updated", "description": "Accreditation status transition" },
{ "event": "accreditation.active", "description": "Account ready to operate — accountId available" },
{ "event": "accreditation.failed", "description": "Accreditation ended without opening an account" },
{ "event": "account.shared_access.granted", "description": "Another tenant started operating an account you already operated" },
{ "event": "policy.violation", "description": "A policy rule was violated — operation rejected (BLOCK), merely reported (NOTIFY_ONLY / ALLOW_AND_NOTIFY), or incoming PIX refunded (AUTO_REFUND)" }
]

The full list also includes PIX out/refund/MED, QR Code, boleto, TED, internal transfers, and fees. Onboarding events are documented in Onboarding Webhooks.

Receiving Flow

  1. An event occurs on our platform.
  2. We send a POST request to the registered URLs.
  3. Your application must process the notification and return a 2xx status.

Configuration Flexibility

Our webhook infrastructure supports various delivery methods:

  • Grouping: You can receive multiple event types (e.g., pix.in.completed and pix.out.completed) at the same URL.
  • Segregation: You can configure different URLs for each event type.
  • Redundancy: We can send the same event to multiple independent URLs simultaneously.
  • Per-account scoping: a subscription can receive only one account’s events — see below.

Per-account subscriptions (accountId)

By default a subscription belongs to the tenant: it receives events from all of your accounts. Passing accountId at creation time makes it receive only that account’s events:

curl -X POST "https://tenant.api.corpx.com/v1/webhooks" \
-H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: tenant-yourcompany" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhooks/acc-123",
"events": ["pix.in.completed", "pix.out.completed"],
"accountId": "acc-123",
"authType": "HMAC",
"secret": "..."
}'

The field comes back in GET /v1/webhooks (accountId: null for tenant-wide subscriptions).

Rules worth knowing before you use it:

  • It is a filter, not extra routing. A subscription with accountId stops receiving the other accounts’ events. If you want everything in one place and a slice in another, create two subscriptions.
  • It cannot be edited. accountId is only accepted at creation. Changing the account of a live subscription would silently redirect one account’s event stream to the endpoint configured for another — creating a new subscription is explicit and leaves the old one auditable.
  • A credential restricted to accounts must provide it. If the credential only reaches a subset of accounts (the case for delegated credentials), omitting accountId returns 422 account_id_required, and pointing at an account outside the subset returns 403 forbidden. Without this, a single-account credential could create a subscription for another account’s movement and read everything over the webhook, bypassing the control the read routes apply.
  • An account from another tenant returns 404 account_not_found.
  • Events with no account (accreditation events, before the account exists) are only delivered to tenant-wide subscriptions.

Security (Destination Authentication)

When creating or updating a webhook subscription, you can choose how our delivery infrastructure authenticates requests to your endpoint. The authentication method is configured per subscription via the API or dashboard.

Available Authentication Methods

MethodauthType valueDescription
HMAC SignatureHMACSigns each request body with your secret using HMAC-SHA256. The signature is sent in the X-Signature header. Recommended.
Platform defaultNONENo key of yours. Deliveries are still signed, but with our infrastructure’s own signature, which you do not control.

You configure the method when creating or updating a subscription, via the API or the Integrator Portal (Webhooks > Edit Subscription).

Rotating or removing the key

The secret is never returned in any response — the subscription only tells you hmacSecretSet: true|false. Because of that:

  • Omitting secret on a PUT keeps the current key. This is what lets you change only the URL without losing the signature.
  • Sending secret replaces the key (rotation).
  • Sending authType: "NONE" deletes the key and stops signing with it. This is the only way to turn it off.

Any other authType value is rejected with 400 unsupported_auth_type.


HMAC Signature Verification

When authType is set to HMAC, each request includes an X-Signature header containing a Base64-encoded HMAC-SHA256 hash of the raw request body, computed using your secret as the key.

Formula:

expected = base64(HMAC_SHA256(your_secret, raw_request_body))

Compare the computed value with the X-Signature header. If they match, the request is authentic.

Always use the raw request body bytes for verification, not a re-parsed/re-serialized version. Re-serializing JSON may change field order or whitespace, which will invalidate the signature.

Verification Examples

Node.js:

const crypto = require("crypto");
function verifySignature(secret, rawBody, signatureHeader) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("base64");
return expected === signatureHeader;
}
// In your Express handler:
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-signature"];
if (!verifySignature(WEBHOOK_SECRET, req.body, signature)) {
return res.status(403).send("Invalid signature");
}
const event = JSON.parse(req.body);
// Process event...
res.sendStatus(200);
});

Python:

import hmac, hashlib, base64
def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
expected = base64.b64encode(
hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(expected, signature_header)

Go:

func verifySignature(secret string, rawBody []byte, signatureHeader string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signatureHeader))
}

Informational Headers

In addition to the authentication headers above, our delivery infrastructure adds the following informational headers to every request:

HeaderDescription
x-hookdeck-event-idDelivery event ID (useful for debugging and support requests).
x-hookdeck-request-idOriginal request ID.
x-hookdeck-attempt-countDelivery attempt number (1 for the first attempt).

These headers are informational and do not need to be validated.

IP Whitelist

To increase the security of your integration, we recommend that your destination server validates the source IP address of incoming requests. Only accept notifications from our infrastructure’s official IPs:

  • 34.138.140.223
  • 34.138.161.100
  • 35.231.250.193
  • 35.196.71.29
  • 34.138.56.192

We suggest adding these addresses to a whitelist in your firewall or web server.

Retries

If your application returns an error (status other than 2xx) or a timeout occurs, our system will attempt to resend the notification following an exponential backoff strategy:

  • Attempts: Up to 6 times.
  • Intervals: Progressively increasing.

After exhausting all attempts, the delivery is marked as failed. You can request a manual retry of that specific delivery.

Retrying a Delivery

To resend a failed delivery, use the per-delivery retry endpoint, passing the subscriptionId and deliveryId (both visible in the subscription’s delivery list):

Request Example:

curl -X POST "https://tenant.api.corpx.com/v1/webhooks/{subscriptionId}/deliveries/{deliveryId}/retry" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-Id: your-tenant-id"

The API queues a new delivery attempt for that deliveryId.

Notification Format

All notifications follow a standard envelope format. The specific content of each event resides in the data object.

Standard Envelope

{
"id": "evt_123456789",
"type": "pix.in.completed",
"occurredAt": "2025-12-29T21:14:33.912Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": { }
}
Amounts are always BRL

Monetary values (amount, paidAmount, etc.) are always in BRL. Decimals use a point (150.50), never a comma. PIX-out events (pix.out.completed, pix.out.failed, pix.out.timeout, and pix.refund.* from the same flow) include data.currency: "BRL". Other events may omit the field — the amount is still in reais.

HTTP envelope vs. portal JSON

The POST to your URL is the envelope (id, type, occurredAt, schemaVersion, environment, tenantId, accountId, data). In the integrator portal, the “webhook delivered” JSON shows only the data object (sometimes labeled deliveredData) — that is not the HTTP body.

Common fields inside data

A handful of fields appear in every event type that represents a ledger entry (PIX IN/OUT, refund, QR paid, internal transfer, fee). They are the recommended way to correlate webhooks with other API endpoints:

FieldDescription
transactionIdTransaction identifier in the format used by our API. Matches what’s returned by GET /v1/accounts/{accountId}/statement and by the synchronous response of the endpoint that originated the transaction. Use it to fetch the transaction on our APIs.
coreIdUUID of the ledger line at the banking partner’s core. Same value exposed in the coreId field of the statement; remains stable across webhook and async statement sync. Use it to reconcile with partner exports.
endToEnd / endToEndIdCentral Bank PIX E2E ID (PIX events only).
statusTransaction state. Standardized vocabulary (see table below).

The first three fields can coexist in the same payload. If only one is present it means the other isn’t relevant for that event type (e.g., internal transfers carry transactionId + coreId but not endToEnd).

data.status values

The status field uses a standardized vocabulary across all events:

StatusMeaningEvents
SUCCESSOperation completed successfully; balance debited/credited.pix.in.completed, pix.out.completed, pix.refund.completed, pix.refund.received, qrcode.paid, boleto.paid, transfer.internal.in, transfer.internal.out, fee.charged, fee.refunded
FAILEDOperation failed. Balance was not moved (or was returned). Check data.error for details.pix.out.failed, pix.refund.failed, boleto.failed
TIMEOUTPartner call was sent successfully but response timed out. Check the statement before retrying with a new idempotency key.pix.out.timeout
EXPIREDDynamic QR Code expired without being paid within the configured expiration.qrcode.expired
CANCELLEDDynamic QR Code cancelled by an explicit integrator call (DELETE /v1/accounts/{accountId}/pix/qr-code?identifier=...).qrcode.cancelled
REVERSEDA previously completed PIX IN that has been reversed (e.g., full chargeback). Appears when late reconciliation overrides an already-delivered PIX IN.pix.in.completed (rare, post-reconciliation)

For MED (dispute) events, status uses its own vocabulary:

StatusMeaning
OPENDispute opened by the claimant, within the 48-hour window for your response.
PENDING_DECISIONYour response deadline has passed — the dispute is still alive, but the decision is no longer in your hands.
ACCEPTEDDispute accepted — funds returned (fully or partially).
REJECTEDDispute rejected — funds stay with the payee.
CANCELEDDispute canceled by whoever filed it.

Events: pix.med.opened, pix.med.updated.

This status is the state at the instant of the event, and in that it is reliable: it is the settlement bank’s own notification. That is why it does not exist in GET /v1/accounts/{id}/pix/med — there it would be a stored value, with no lookup to confirm it. If you need to track the state of a dispute, accumulate these events: they are the only source.

status guarantee

status is always present in events that represent a ledger entry. If you receive a webhook missing the field (or with an empty value), it’s a purely informational event (e.g., account.balance_updated) — in that case use the type field itself to determine semantics.

Date and time format

Date/time fields use ISO 8601 / RFC 3339 with an explicit offset — read the offset, don’t assume. Webhooks (envelope occurredAt and fields inside data such as receivedAt, completedAt, initiatedAt, chargedAt, openedAt) are always UTC (Z). In REST responses, “our-side” fields (createdAt, updatedAt, reconciledAt, fetchedAt, balance) are UTC (Z) too; the transaction time in the statement and lookups — timestamp (/statement, /pix/transactions) and occurredAt (/pix/payments/lookup, boleto, fee.occurredAt) — is in São Paulo time (-03:00).

Examples:

2026-04-29T23:53:55.001Z ← with milliseconds
2026-04-29T23:53:49.328720Z ← with microseconds
2026-04-30T18:04:36Z ← no fractional seconds

We never emit timestamps in Brazilian local time (BRT) nor “naive” timestamps (without timezone indicator). If you ever receive a date field without the Z suffix, treat it as a bug and report it — we will normalize as soon as we identify the source.


Event Types and Contents (data)

1. pix.in.completed

Sent when a PIX is successfully received (inbound) into the account.

Fields always present in data:

  • transactionId: Ledger transaction ID (the settlement-bank operation id when available; otherwise pix-in-{endToEnd}). Matches GET /v1/accounts/{accountId}/statement. The envelope id is pix-in-{endToEnd}.
  • endToEnd: Unique transaction ID at the Central Bank.
  • accountId / tenantId: repeated inside data (they are also on the envelope).
  • amount: Transaction amount in reais.
  • status: "SUCCESS".
  • method: How the payer started the PIX — STATIC_QR_CODE, DYNAMIC_QR_CODE, DICT (PIX key) or MANUAL (bank details).
  • receivedAt: Timestamp of receipt (RFC 3339 UTC).

Conditional fields:

  • identifier: When the credit came from a QR Code, this is the txid of the QR that was paid — use it to reconcile with the QR you created.
  • description: Description from the settlement bank (may be an empty string).
  • payer: Sender. Only keys with a value are included: name, document, bankCode, bankIspb, branch, account / accountNumber (same value; accountNumber is a compatibility alias).
  • payee: Receiver (your account). Same keys, plus pixKey when available. Omitted if the settlement bank sent no payee data.

This event does not send reconciliationId.

Full Example:

{
"id": "pix-in-E0000000020251229211433912",
"type": "pix.in.completed",
"occurredAt": "2025-12-29T21:14:33.912Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"transactionId": "pix-in-E0000000020251229211433912",
"endToEnd": "E0000000020251229211433912",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"amount": 150.50,
"description": "",
"identifier": "SEV798c4a1f4b2e4d9c8a1b2c3d4",
"method": "STATIC_QR_CODE",
"status": "SUCCESS",
"receivedAt": "2025-12-29T21:14:33.900Z",
"payer": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"bankIspb": "00000000",
"branch": "0001",
"account": "12345-6",
"accountNumber": "12345-6"
},
"payee": {
"name": "Test Company",
"document": "12345678000199",
"bankCode": "681",
"bankIspb": "50871921",
"branch": "0001",
"account": "98765-4",
"accountNumber": "98765-4"
}
}
}

2. qrcode.paid

Sent when a QR Code generated by you is paid. For the same credit you also receive pix.in.completed — this event is the QR-specific confirmation (identifier / qrcodeId).

Fields always present in data:

  • qrcodeId: Internal QR Code ID (for a static QR this is the same as identifier).
  • identifier: txid (identifier) of the QR Code you created.
  • accountId / tenantId: Account that owns the charge.
  • type: "static" or "dynamic".
  • amount: Amount paid.
  • endToEnd: Unique transaction ID at the Central Bank.
  • transactionId: Settlement-bank transaction ID, when available.
  • status: "SUCCESS".
  • receivedAt: Timestamp of receipt (RFC 3339 UTC).

Conditional fields:

  • payer: Payer. Dynamic QR typically carries name and document; static QR may also include bankCode, bankIspb, branch, accountNumber.
  • payee: Receiver (your account). Present on static QR when the settlement bank sent those fields.

This event does not send reconciliationId.

Full Example:

{
"id": "qrcode-paid-qr_abc123",
"type": "qrcode.paid",
"occurredAt": "2025-12-29T21:15:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"qrcodeId": "qr_abc123",
"identifier": "txid-qr-123",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"type": "dynamic",
"amount": 250.00,
"endToEnd": "E0000000020251229211500000",
"transactionId": "162982-pix",
"status": "SUCCESS",
"receivedAt": "2025-12-29T21:14:33.900Z",
"payer": {
"name": "Maria Oliveira",
"document": "98765432100"
}
}
}

3. pix.out.completed

Sent when an outbound PIX transfer is completed successfully.

The POST to your URL is the envelope (id, type, occurredAt, schemaVersion, environment, tenantId, accountId, data). In the integrator portal, the “webhook delivered” JSON shows only the data object (sometimes labeled deliveredData) — that is not the HTTP body.

Fields always present in data:

  • paymentId: Order ID (same as the 202 response of the async POST). The envelope id is pix-out-{paymentId}.
  • tenantId, accountId: repeated inside data (they are also on the envelope).
  • status: "SUCCESS".
  • endToEnd: BACEN E2E ID (empty string if the settlement bank has not issued one yet).
  • transactionId: Settlement-bank transaction ID, when available.
  • amount: Amount in reais.
  • currency: always "BRL".
  • description: Description sent on the POST (may be an empty string).
  • identifier: Your reconciliation key.
  • originalTransactionId: Original transaction ID on refunds; empty string on a regular PIX out.
  • initiatedAt / completedAt: RFC 3339 UTC (Z), with fractional seconds (up to nanoseconds).

Conditional fields:

  • key: { "type", "key" } when the transfer was sent to a PIX key.
  • payee: Destination. Only keys with a value are included: name, document, bankCode, bankIspb, branch, accountNumber, pixKey, accountType.
  • payer: Origin account. Present only when the settlement-bank webhook carried those fields — do not assume it always arrives.
  • late: true only when confirmation arrived after a pix.out.timeout for the same paymentId. Absent in the normal case. See Late confirmation.

This event does not send reconciliationId, method, or a payment object.

Full Example:

{
"id": "pix-out-9ccd1869-7593-4feb-9602-e525e818ab8e",
"type": "pix.out.completed",
"occurredAt": "2026-09-16T20:58:46.258668746Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"paymentId": "9ccd1869-7593-4feb-9602-e525e818ab8e",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"status": "SUCCESS",
"endToEnd": "E508719212026091620583702716219",
"transactionId": "162982-pix",
"amount": 50.00,
"currency": "BRL",
"description": "Supplier payment",
"identifier": "transfer-001",
"originalTransactionId": "",
"initiatedAt": "2026-09-16T20:58:42.646763686Z",
"completedAt": "2026-09-16T20:58:46.258668746Z",
"key": {
"type": "EMAIL",
"key": "destinatario@exemplo.com"
},
"payer": {
"name": "ACME LTDA",
"document": "12345678000199",
"bankCode": "208",
"bankIspb": "50871921",
"branch": "1",
"accountNumber": "3811084"
},
"payee": {
"name": "JOAO SILVA",
"document": "12345678900",
"bankCode": "077",
"bankIspb": "00416968",
"branch": "1",
"accountNumber": "0013887580"
}
}
}

4. pix.out.failed

Sent when an outbound PIX transfer fails definitively — the banking partner rejected the operation (insufficient funds, invalid key, anti-fraud, BACEN rejection etc.) and no balance was moved (or it was returned).

Terminal event

pix.out.failed is terminal: once received, no later pix.out.completed will arrive for the same order. Uncertainty scenarios (e.g. communication timeout with the partner, outcome unconfirmed) emit pix.out.timeout — never pix.out.failed.

data uses the same fields as pix.out.completed, plus:

  • status: "FAILED".
  • error / errorCode / errorReason: rejection reason (error is the friendly alias).
  • partner: raw settlement-bank error, when available.
  • late: true only when the rejection arrived after a pix.out.timeout for the same paymentId. See Late confirmation.

This event does not send reconciliationId.

Full Example:

{
"id": "pix-out-9ccd1869-7593-4feb-9602-e525e818ab8e",
"type": "pix.out.failed",
"occurredAt": "2026-09-16T20:58:46.258668746Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"paymentId": "9ccd1869-7593-4feb-9602-e525e818ab8e",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"status": "FAILED",
"endToEnd": "E508719212026091620583702716219",
"transactionId": "162982-pix",
"amount": 1000.00,
"currency": "BRL",
"description": "Supplier payment",
"identifier": "transfer-002",
"originalTransactionId": "",
"initiatedAt": "2026-09-16T20:58:42.646763686Z",
"completedAt": "2026-09-16T20:58:46.258668746Z",
"error": "insufficient balance",
"errorCode": "insufficient_balance",
"errorReason": "insufficient balance",
"key": {
"type": "EMAIL",
"key": "destinatario@exemplo.com"
},
"payee": {
"name": "JOAO SILVA",
"document": "12345678900",
"bankCode": "077",
"bankIspb": "00416968",
"branch": "1",
"accountNumber": "0013887580"
}
}
}

5. payment.sent (removed)

Removed in v1.28.0 (2026-04-17)

This event was deprecated in v1.15.0 (2026-02-20) and has been permanently disabled in v1.28.0 (2026-04-17). Use pix.out.completed with method: PAYMENT and the payment object in the payload instead.

6. payment.refunded (removed)

Removed in v1.28.0 (2026-04-17)

This event was deprecated in v1.15.0 (2026-02-20) and has been permanently disabled in v1.28.0 (2026-04-17). Use pix.refund.completed instead. That event does not include originalEndToEnd or refundEndToEnd — the refund E2E is endToEnd, and the original transaction is originalTransactionId.

7. pix.refund.completed

Sent when a PIX refund we initiated is completed. Different from pix.refund.received, which is a return started by the original payee without us asking.

data uses the same fields as pix.out.completed, with:

  • Envelope id: pix-refund-{paymentId}.
  • status: "SUCCESS".
  • originalTransactionId: ID of the original PIX IN being refunded (not empty, unlike a regular PIX out).

This event does not send originalEndToEnd, refundEndToEnd, or reconciliationId. To correlate with the original credit, use originalTransactionId / identifier and the statement.

Full Example:

{
"id": "pix-refund-9ccd1869-7593-4feb-9602-e525e818ab8e",
"type": "pix.refund.completed",
"occurredAt": "2025-12-29T21:14:53.900Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"paymentId": "9ccd1869-7593-4feb-9602-e525e818ab8e",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"status": "SUCCESS",
"endToEnd": "D0000000020251229211453900",
"transactionId": "162982-pix",
"amount": 150.50,
"currency": "BRL",
"description": "",
"identifier": "refund-999",
"originalTransactionId": "pix-in-E0000000020251229211433912",
"initiatedAt": "2025-12-29T21:14:33.900Z",
"completedAt": "2025-12-29T21:14:53.900Z",
"payer": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"payee": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
}
}
}

8. pix.refund.failed

Sent when a refund we initiated fails definitively.

data uses the same fields as pix.refund.completed / pix.out.failed, with:

  • status: "FAILED".
  • error / errorCode / errorReason: rejection reason (error is the friendly alias).
  • partner: raw settlement-bank error, when available.

This event does not send originalEndToEnd, refundEndToEnd, or reconciliationId.

Full Example:

{
"id": "pix-refund-9ccd1869-7593-4feb-9602-e525e818ab8e",
"type": "pix.refund.failed",
"occurredAt": "2025-12-29T21:14:35.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"paymentId": "9ccd1869-7593-4feb-9602-e525e818ab8e",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"status": "FAILED",
"endToEnd": "E0000000020251229211433912",
"transactionId": "162982-pix",
"amount": 150.50,
"currency": "BRL",
"description": "",
"identifier": "refund-998",
"originalTransactionId": "pix-in-E0000000020251229211433912",
"initiatedAt": "2025-12-29T21:14:33.900Z",
"completedAt": "2025-12-29T21:14:35.000Z",
"error": "Original transaction has already been refunded",
"errorCode": "already_refunded",
"errorReason": "Original transaction has already been refunded",
"payer": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"payee": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
}
}
}

9. fee.charged

Sent when a banking fee is charged to the account (e.g., per-transaction PIX fee).

data structure:

  • transactionId: Deterministic ledger row id (fee-{coreId}).
  • accountId / tenantId: Charged account and tenant.
  • amount: Fee amount, always positive in the account currency.
  • description: Fee description.
  • feeServiceType: Underlying service that triggered the fee (PIX, BOLETO, …) when the bank reports it.
  • originalRef / transactionRef: Cross reference to the originating movement when available.
  • occurredAt: Timestamp at which the bank booked the fee.
  • status: Always "SUCCESS".
  • kind: "CHARGED".

Full Example:

{
"id": "evt_fee_123",
"type": "fee.charged",
"occurredAt": "2026-02-14T20:30:43.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"transactionId": "fee-9c1f...e2",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"amount": 2.99,
"description": "TARIFA",
"feeServiceType": "PIX",
"originalRef": "tx_38a1b2",
"transactionRef": "tx_38a1b2",
"occurredAt": "2026-02-14T20:30:43.000Z",
"status": "SUCCESS",
"kind": "CHARGED"
}
}

9.1. fee.refunded

Sent when the bank reverses a previously charged fee (cashback, adjustment). Same shape as fee.charged, with kind="REFUNDED". Funds are already credited to the account.

{
"id": "evt_fee_456",
"type": "fee.refunded",
"occurredAt": "2026-02-15T13:42:11.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"transactionId": "fee-7c2a...11",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"amount": 2.99,
"description": "ESTORNO DE TARIFA",
"feeServiceType": "PIX",
"originalRef": "fee-9c1f...e2",
"occurredAt": "2026-02-15T13:42:11.000Z",
"status": "SUCCESS",
"kind": "REFUNDED"
}
}

10. pix.med.opened

Sent when a new MED (Special Return Mechanism) dispute is opened against a transaction credited to one of your accounts. It starts the 48-hour clock for your response — see the Disputes guide.

data Structure:

  • medId: Dispute identifier (infraction report). It is the {medId} used by the REST routes.
  • originalEndToEnd: endToEndId of the disputed PIX transaction.
  • amount: Disputed amount.
  • reasonCode: Reason reported by the scheme, raw (e.g. scam-or-fraud, unauthorized-transaction).
  • claimMessage: Free-text details provided by the claimant (when present).
  • openedAtIso: MED opening date (ISO 8601).
  • clientAnswerDeadlineIso: Your response deadline — openedAtIso + 48h.
  • status: OPEN on filing.

Full Example:

{
"id": "evt_med_123",
"type": "pix.med.opened",
"occurredAt": "2026-07-21T17:55:59.601Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"medId": "c40a8974-4b4c-47c5-9d4b-81376e9071c6",
"originalEndToEnd": "E0000000020251229211433912",
"amount": 150.50,
"reasonCode": "scam-or-fraud",
"claimMessage": "See contacts provided in FundsRecovery",
"openedAtIso": "2026-07-21T17:55:59.601Z",
"clientAnswerDeadlineIso": "2026-07-23T17:55:59.601Z",
"status": "OPEN"
}
}

10. pix.med.updated

Sent when the status of a MED dispute changes. This is how you learn the outcome, and it is the only source of state: POST .../answer merely records your defense (which goes on for analysis outside the API), and the dispute also moves through action by BACEN and by the claimant’s bank.

A redelivery of the same status produces no new event, and a late event (settlement-bank timestamp older than what we already stored) is dropped — the status never moves backwards.

data Structure:

  • medId: Dispute identifier (same as pix.med.opened).
  • originalEndToEnd: endToEndId of the disputed PIX transaction.
  • amount: Disputed amount.
  • reasonCode: Reported reason.
  • clientAnswerDeadlineIso: Your response deadline (the same as on filing).
  • status: Current MED status — dedicated vocabulary (OPEN, PENDING_DECISION, ACCEPTED, REJECTED, CANCELED; see the status table above).

Settlement bank status mapping — the raw value never leaves in the event:

Settlement bank statusdata.status
under-client-analysis, receivedOPEN
client-analysis-delayPENDING_DECISION
closed-full-refund, closed-partial-refundACCEPTED
closed-no-refundREJECTED
canceledCANCELED

An unknown status becomes OPEN, not a closure: missing the filing of a dispute costs you your response window, while receiving one extra only costs a lookup.

Full Example:

{
"id": "evt_med_456",
"type": "pix.med.updated",
"occurredAt": "2026-07-24T01:15:03.410Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"medId": "c40a8974-4b4c-47c5-9d4b-81376e9071c6",
"originalEndToEnd": "E0000000020251229211433912",
"amount": 150.50,
"reasonCode": "scam-or-fraud",
"status": "CANCELED"
}
}

11. transfer.internal.in

Sent to the account that receives an internal transfer between accounts of the same banking ecosystem. Internal transfers are instant and settled at the same bank — they do not travel through the PIX SPI, so they have no endToEndId.

data structure:

  • transactionId: Transaction ID in our system (format internal-{uuid}). Use it on GET /v1/accounts/{accountId}/statement.
  • amount: Amount received.
  • status: Always SUCCESS (internal transfers are atomic).
  • completedAt: Settlement timestamp.
  • identifier: Identifier the sender supplied on POST /transfers/internal* (when given). The same value arrives on both legs (transfer.internal.out and transfer.internal.in).
  • payerDescription / description: Description supplied by the sender.
  • source: The payer — name, taxId, providerAccountId and, when the account is managed by us, accountId and tenantId.
  • destination: The receiver, with the same fields.
  • payer / receiver: The same two parties, with document in place of taxId. They appear only when the transfer was initiated outside the API (the liquidator’s app or console). Kept for compatibility — prefer source/destination, which arrive from both origins.
Destination outside CorpX

When the receiver is an account at another bank, destination carries name and taxId (the ones you supplied in the request) without accountId/tenantId. In that case only transfer.internal.out exists.

Full Example:

{
"id": "evt_int_in_001",
"type": "transfer.internal.in",
"occurredAt": "2026-04-23T14:21:05.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "773107de-139e-48d1-9462-f4e88f251891",
"data": {
"transactionId": "internal-9d4a5b7c-1234-4abc-9876-abc123456789",
"amount": 500.00,
"status": "SUCCESS",
"completedAt": "2026-04-23T14:21:05.000Z",
"identifier": "int-transfer-12345",
"payerDescription": "Pagamento salário",
"source": {
"providerAccountId": "a1b2c3d4-...",
"tenantId": "tenant-acme",
"accountId": "e5f6g7h8-...",
"name": "EMPRESA ORIGEM LTDA",
"taxId": "12345678000199"
},
"destination": {
"providerAccountId": "b2c3d4e5-...",
"tenantId": "tenant-acme",
"accountId": "773107de-139e-48d1-9462-f4e88f251891",
"name": "MARIA SILVA",
"taxId": "12345678900"
}
}
}

12. transfer.internal.out

Sent to the account that sends an internal transfer. Same shape as transfer.internal.in, with two differences:

  • identifier is populated on both legs when you passed identifier in the original request (POST /v1/accounts/{accountId}/transfers/internal*). Use it to reconcile the transfer on your side.
  • description — if you passed description in the request, that same text comes back here (instead of the default Transferência Interna).

Example:

{
"id": "evt_int_out_001",
"type": "transfer.internal.out",
"occurredAt": "2026-04-23T14:21:05.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "a1b2c3d4-aaaa-bbbb-cccc-111122223333",
"data": {
"transactionId": "internal-9d4a5b7c-1234-4abc-9876-abc123456789",
"amount": 500.00,
"status": "SUCCESS",
"completedAt": "2026-04-23T14:21:05.000Z",
"identifier": "int-transfer-12345",
"description": "Pagamento salário",
"source": {
"providerAccountId": "a1b2c3d4-...",
"tenantId": "tenant-acme",
"accountId": "a1b2c3d4-aaaa-bbbb-cccc-111122223333",
"name": "EMPRESA ORIGEM LTDA",
"taxId": "12345678000199"
},
"destination": {
"providerAccountId": "b2c3d4e5-...",
"tenantId": "tenant-acme",
"accountId": "773107de-139e-48d1-9462-f4e88f251891",
"name": "MARIA SILVA",
"taxId": "12345678900"
}
}
}
How to reconcile an internal transfer

When calling POST /v1/accounts/{accountId}/transfers/internal (or the by-document / by-bank-account variants), send your own unique identifier. The API response returns that identifier together with transactionId. The transfer.internal.out webhook arrives afterwards with the same identifier + transactionId, so you can close the transaction on your side without querying the statement.

13. pix.out.timeout

Sent when the order outcome is indeterminate at the end of the confirmation window: the partner call was dispatched but the response/final confirmation did not arrive in time (this includes communication timeouts on the submit call itself). Not a definitive failure — a late pix.out.completed or pix.out.failed may still arrive afterwards. Check the statement before retrying with a new idempotency key; retrying with the same key is safe.

data structure (fields specific to this event):

  • status: Always "TIMEOUT".
  • errorCode / errorReason / warning / error: What to do next (confirmation_timeout).
  • hold: Present when the order is being held at the settlement bank. Carries owner: "partner" and reason (partner_authorization = the settlement bank’s internal authorisation queue; partner_risk_analysis = risk review; partner_unspecified = held, queue unknown). When this object is present, there is nothing to approve on your side or on ours — the decision belongs to the settlement bank.
  • partnerStatus / partnerStatusId: Raw snapshot of the state at the settlement bank, useful to attach to a support ticket. These are the settlement bank’s values, not our canonical vocabulary: do not use them in status comparisons.
{
"id": "evt_pix_out_timeout_001",
"type": "pix.out.timeout",
"occurredAt": "2026-05-04T10:42:11.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"paymentId": "pay_9ccd1869-7593-4feb-9602-e525e818ab8e",
"transactionId": "162982-pix",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"endToEnd": "E0000000020260504104211",
"amount": 250.00,
"currency": "BRL",
"status": "TIMEOUT",
"errorCode": "confirmation_timeout",
"errorReason": "partner response timed out. The call to send the money was accepted, but the final status confirmation has not arrived yet.",
"error": "partner response timed out. The call to send the money was accepted, but the final status confirmation has not arrived yet.",
"warning": "partner response timed out. The call to send the money was accepted, but the final status confirmation has not arrived yet.",
"identifier": "order-12345",
"partnerStatus": "awaiting-authorization",
"partnerStatusId": 7,
"hold": {
"owner": "partner",
"reason": "partner_authorization"
}
}
}

Late confirmation (late: true)

TIMEOUT is an indeterminate state, not a final one. When the order is held at the settlement bank, we keep polling it for up to 7 days, and if it settles (or rejects) within that window you receive the matching terminal event:

  • pix.out.completed with late: true — the money left the account.
  • pix.out.failed with late: true — the settlement bank rejected it.

The late event carries the same paymentId as the pix.out.timeout you already received: it corrects that outcome, it is not a second payment. Treat it as the order’s definitive outcome.

After 7 days without news no further event is sent and the order stays TIMEOUT — which literally means “we do not know”. Check the statement (GET /v1/accounts/{accountId}/statement) before reissuing. Reissuing with the same Idempotency-Key is safe; a new key may result in a duplicate payment.

14. pix.refund.received

Sent when the receiver of a PIX out returns the funds on their own initiative (without us asking). Different from pix.refund.completed, which confirms a refund we triggered.

data structure:

  • transactionId: Deterministic ID of the received refund row (pix-refund-received-{refundEndToEnd}).
  • refundEndToEnd: Refund D-code (BACEN).
  • originalEndToEnd: E-code of the original PIX out being reversed.
  • originalIdentifier: Identifier of the original PIX out (echo of the value sent at payment creation), when available.
  • accountId / tenantId: Account that received the refund.
  • amount: Reversed amount.
  • description: Description from the partner.
  • receivedAt: Refund timestamp.
  • payer: Party that returned the funds (counterparty / original PIX out recipient): name, document, bank, branch, account.
  • payee: Party that received the credit (our account / original PIX out payer): name, document, bank, branch, account.
  • status: Always "SUCCESS".
{
"id": "evt_pix_refund_recv_001",
"type": "pix.refund.received",
"occurredAt": "2026-05-04T11:11:43.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"transactionId": "pix-refund-received-D2026050420260504...",
"refundEndToEnd": "D2026050420260504000000001",
"originalEndToEnd": "E0000000020260503100000001",
"originalIdentifier": "client-refund-key-001",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"amount": 320.00,
"description": "PIX refund",
"receivedAt": "2026-05-04T11:11:43.000Z",
"payer": {
"name": "João Silva",
"document": "12345678901",
"bankCode": "341",
"bankIspb": "60701190",
"branch": "0001",
"accountNumber": "12345-6"
},
"payee": {
"name": "Acme Corp LTDA",
"document": "12345678000199",
"bankCode": "681",
"bankIspb": "50871921",
"branch": "0001",
"accountNumber": "98765-4"
},
"status": "SUCCESS"
}
}

15. qrcode.expired

Sent when a dynamic QR Code expires without being paid within the configured expiration.

data structure:

  • qrcodeId: Internal QR Code ID.
  • identifier: txid of the QR Code you created.
  • accountId / tenantId: Account that owns the charge.
  • type: "dynamic" (expiration only applies to dynamic QR Codes).
  • amount: Amount that was awaiting payment.
  • status: Always "EXPIRED".
{
"id": "qrcode-expired-qr_abc123",
"type": "qrcode.expired",
"occurredAt": "2026-05-04T12:00:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"qrcodeId": "qr_abc123",
"identifier": "qr_abc123",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"type": "dynamic",
"amount": 99.90,
"status": "EXPIRED"
}
}

16. qrcode.cancelled

Sent when the integrator explicitly cancels a dynamic QR Code via DELETE /v1/accounts/{accountId}/pix/qr-code?identifier={txid} before payment.

data structure:

  • qrcodeId: Internal QR Code ID.
  • identifier: txid of the QR Code you created.
  • accountId / tenantId: Account that owns the charge.
  • type: "dynamic".
  • status: Always "CANCELLED".
  • reason / cancelledBy: Present when the cancellation carries a reason and who cancelled it.
{
"id": "qrcode-cancelled-qr_abc123",
"type": "qrcode.cancelled",
"occurredAt": "2026-05-04T11:45:30.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"qrcodeId": "qr_abc123",
"identifier": "qr_abc123",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"type": "dynamic",
"status": "CANCELLED",
"reason": "cancelled_by_integrator",
"cancelledBy": "integrator"
}
}

17. boleto.paid

Sent when a boleto paid via POST /v1/accounts/{accountId}/boleto/pay is confirmed by the issuing bank.

data structure:

  • paymentId: Canonical boleto id (bol_{uuid}) — same value returned by POST /boleto/pay. Use this to correlate. The envelope id is boleto-{paymentId}.
  • boletoId: Alias of paymentId (same value).
  • partnerId: Settlement-bank reference (operationReferenceId), for support/reconciliation.
  • accountId / tenantId: Payer account.
  • amount: Amount paid.
  • line: Digitable line / barcode.
  • status: Always "SUCCESS".

This event does not send transactionId.

{
"id": "boleto-bol_aabbccdd",
"type": "boleto.paid",
"occurredAt": "2026-05-04T15:00:11.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"paymentId": "bol_aabbccdd",
"boletoId": "bol_aabbccdd",
"partnerId": "op-ref-99887766",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"amount": 1234.50,
"line": "00190.00009 03450.000004 47018.500003 1 88880000123450",
"status": "SUCCESS"
}
}

18. boleto.failed

Sent when the issuer rejects the payment. data.error is a string with the reason (not an object). The same text is also in data.errorReason; data.errorCode is present when the settlement bank sent a code.

{
"id": "boleto-bol_eeffgghh",
"type": "boleto.failed",
"occurredAt": "2026-05-04T15:00:11.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"paymentId": "bol_eeffgghh",
"boletoId": "bol_eeffgghh",
"partnerId": "op-ref-99887766",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"amount": 1234.50,
"line": "00190.00009 03450.000004 47018.500003 1 88880000123450",
"status": "FAILED",
"error": "Boleto vencido — emitir nova cobrança",
"errorReason": "Boleto vencido — emitir nova cobrança"
}
}

19. ted.out.requested

Sent immediately after POST /v1/accounts/{id}/ted/out is accepted. Status PROCESSING — TED registered at the settlement bank, waiting for the BACEN window to settle. See the TED guide.

{
"id": "evt_ted_out_requested_001",
"type": "ted.out.requested",
"occurredAt": "2026-05-23T09:00:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"tedId": "ted-fornecedor-acme-001",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"identifier": "ted-fornecedor-acme-001",
"partnerId": "",
"amount": 5000.00,
"destination": {
"bankCode": "001",
"branch": "1234",
"account": "56789",
"accountType": "CHECKING",
"taxNumber": "12345678900",
"holderName": "JOAO DA SILVA"
},
"description": "Pagamento de fornecedor NF 12345"
}
}

20. ted.out.confirmed

Settlement confirmed by the settlement bank (terminal — success).

{
"id": "evt_ted_out_confirmed_001",
"type": "ted.out.confirmed",
"occurredAt": "2026-05-23T09:32:15.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"tedId": "ted-fornecedor-acme-001",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"identifier": "ted-fornecedor-acme-001",
"partnerId": "fornecedor-acme-001",
"amount": 5000.00,
"destination": { "bankCode": "001", "branch": "1234", "account": "56789", "accountType": "CHECKING", "taxNumber": "12345678900", "holderName": "JOAO DA SILVA" },
"description": "Pagamento de fornecedor NF 12345"
}
}

Deprecated alias: the ted.payment event is dispatched in parallel with ted.out.confirmed with the same payload, kept only for compatibility with legacy subscriptions. New integrators should subscribe to ted.out.confirmed only. ted.payment will be removed in v3.0.

21. ted.out.failed

Settlement rejected/expired (terminal — failure). data.errorReason carries the canonical reason.

{
"id": "evt_ted_out_failed_001",
"type": "ted.out.failed",
"occurredAt": "2026-05-23T09:40:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"tedId": "ted-fornecedor-acme-001",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"identifier": "ted-fornecedor-acme-001",
"partnerId": "fornecedor-acme-001",
"amount": 5000.00,
"destination": { "bankCode": "999", "branch": "1234", "account": "56789", "accountType": "CHECKING", "taxNumber": "12345678900", "holderName": "JOAO DA SILVA" },
"description": "Pagamento de fornecedor NF 12345",
"errorReason": "invalid_bank_code",
"error": "invalid_bank_code"
}
}

Possible errorReason values: invalid_bank_code, insufficient_funds, outside_banking_hours, bank_unreachable, limit_exceeded, or timeout aguardando confirmação do parceiro (>48h) (defensive polling exhausted).

22. ted.in.received

A TED was received in your account (originated by a third party). Includes data.payer (name, document, bank, branch, account) when the settlement bank provides those fields — use them to reconcile with your books.

{
"id": "evt_ted_in_received_001",
"type": "ted.in.received",
"occurredAt": "2026-05-23T14:15:22.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"transactionId": "abc123def",
"identifier": "TED-MT-9876",
"partnerTxId": "abc123def",
"accountId": "acc_123456",
"tenantId": "tenant-acme",
"amount": 1200.00,
"description": "Pagamento recebido",
"receivedAt": "2026-05-23T14:15:22Z",
"payer": {
"name": "EMPRESA XYZ LTDA",
"document": "98765432000110",
"bankCode": "237",
"bankIspb": "60746948",
"branch": "0001",
"account": "123456",
"accountNumber": "123456"
}
}
}

Remember: for third parties to send a TED to your account, they must use bank 681 (MT Instituição de Pagamentos) — see the TED guide.

23. policy.violation

A policy rule configured for your account or tenant was violated. The event covers every policy section — PIX out, PIX in, QR Code, keys, and refunds — and is sent both when the operation is rejected (action: "BLOCK") and when it only produces a warning (NOTIFY_ONLY in monitor mode, ALLOW_AND_NOTIFY and AUTO_REFUND on incoming PIX). Use blocked to tell them apart without interpreting the action.

{
"id": "evt_policy_violation_001",
"type": "policy.violation",
"occurredAt": "2026-05-23T22:41:10.000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-acme",
"accountId": "acc_123456",
"data": {
"phase": "counterparty",
"action": "BLOCK",
"blocked": true,
"paymentId": "pay_9f2c...",
"accountId": "acc_123456",
"identifier": "ORDER-1234",
"mode": "KEY",
"amount": 5000.00,
"violations": [
{ "rule": "cpfCnpjBlacklist", "message": "recipient document is blacklisted by policy" }
],
"counterparty": { "document": "12345678900", "name": "JOHN DOE" }
}
}
FieldDescription
phaseedge (rejected in the response, HTTP 422), counterparty (after resolving the recipient, during processing), pix_in (after an incoming PIX was credited), or dict_lookup (key lookup rejected by limit)
actionBLOCK, NOTIFY_ONLY, ALLOW_AND_NOTIFY, or AUTO_REFUND, as configured in the policy
blockedtrue when the operation was actually rejected. Always false for pix_in
modeKEY, BANK_ACCOUNT, QRCODE, or REFUND on PIX out; the operation subtype in the other sections (static/dynamic for QR, the key type, the incoming PIX method)
violations[].rulePIX out rules carry no prefix (pixOutDisabled, cpfCnpjBlacklist, sameOwnershipOnly, operatingHours, maxAmount, nightMaxAmount, allowedPersonTypes); the other sections are prefixed (pixIn.*, qrCode.*, keys.*, refund.*) or dictLookup.*
counterpartyThe recipient (PIX out) or the payer (PIX in), when known

When blocked is true on a PIX out, you also receive the matching pix.out.failed, with errorCode: policy_denied. They are two events with distinct ids: this one is the rule warning, that one is the payment outcome.

In monitor mode (NOTIFY_ONLY) the payment goes through normally and will have its own outcome (pix.out.completed, for example) — this event is only a heads-up that the transaction would have been rejected had the rule been set to BLOCK.

Violation on an incoming PIX (phase: "pix_in")

An incoming PIX cannot be refused: the settlement bank credits the account and only then tells us. The violation always comes after the credit, and the payload swaps paymentId for transactionId and endToEnd, adding refunded:

{
"phase": "pix_in",
"action": "AUTO_REFUND",
"blocked": false,
"refunded": true,
"transactionId": "tx_8a1b...",
"accountId": "acc_123456",
"endToEnd": "E18236120202607291230abcdef1234",
"amount": 5000.00,
"mode": "DICT",
"violations": [
{ "rule": "pixIn.cpfCnpjBlacklist", "message": "payer document is blacklisted by policy" }
],
"counterparty": { "document": "12345678900", "name": "JOHN DOE", "bankCode": "260" }
}

pix.in.completed is delivered either way, and before this event. With refunded: true, the refund then produces the usual PIX out events (pix.out.completed or pix.out.failed).

Rule and configuration details are in the Policies and Rules guide.

Best Practices

  • Idempotency: Your application should be prepared to receive the same webhook more than once. Use the envelope id to avoid duplicate processing.
  • Quick Response: Return a 200 OK status as soon as you receive the webhook and process the business logic asynchronously to avoid timeouts.
  • Timestamp Validation: Check that the occurredAt is not too old (we recommend a 5-minute tolerance) to prevent replay attacks.