Webhook Payloads
Examples of the payloads sent for each event type. All webhooks are sent as HTTP POST with Content-Type: application/json.
Security headers
Each notification includes the headers X-Owem-Signature (HMAC-SHA256), X-Owem-Timestamp, X-Owem-Event-Id, and X-Owem-Event-Type. See Webhooks — Overview for details on validation.
Status Reference
Not every event means the transaction is complete. Use the table below to know when money has actually been settled.
| Event | Status | Meaning | Money settled? |
|---|---|---|---|
pix.charge.created | created | QR code generated or cash-in initiated. Awaiting payment. | No — only created |
pix.charge.paid | paid | PIX received and settled in the account. Balance updated, fee charged. | Yes |
pix.charge.expired | expired | QR code expired without payment. | N/A |
pix.charge.cancelled | cancelled | QR code explicitly cancelled by the merchant before payment. | N/A |
pix.payout.queued | queued | PIX send queued by rate limit (ClientLimiter or DICT bucket). No debit yet. | No — awaiting quota |
pix.payout.processing | processing | PIX sent, awaiting destination confirmation. Balance on hold. | No — may reverse |
pix.payout.confirmed | settled | PIX sent and confirmed by the destination. Definitive debit. | Yes |
pix.payout.failed | rejected | PIX send rejected by the destination. Hold released, balance restored. | No |
pix.payout.returned | returned | Sent PIX was returned after settlement. | Yes (reversal) |
pix.refund.requested | requested | PIX refund requested (MED). Preventive block created. | Partial |
pix.refund.completed | settled / completed | PIX refund completed and settled. Definitive debit. | Yes |
pix.return.received | settled | PIX return received and settled (credit into the account). | Yes |
pix.infraction.created | ACKNOWLEDGED | PIX infraction reported against you. Requires action. | Partial — preventive block if >R$1k |
pix.infraction.resolved | CLOSED / CANCELLED | Infraction resolved (refund executed or denied). | N/A — effect on another event |
pix.infraction.defense_submitted | defense_submitted | Defense submitted by the merchant. Awaiting BACEN. | N/A |
webhook.test | test | Test event dispatched manually via Admin/Merchant portal. | N/A |
Reconciliation rules:
- Consider balance inflows only on the statuses:
paid(PIX IN credit) andreturned(reversal of a previously sent PIX OUT). - Consider balance outflows only on the statuses:
settled(confirmed PIX OUT debit) andcompleted(final MED refund debit), andsettledonpix.return.received(reversal of a previously received PIX IN). - All other statuses (
created,queued,processing,rejected,expired,requested,ACKNOWLEDGED,defense_submitted, etc.) are intermediate — they do not trigger accounting movement on your side. - Do not treat
pix.payout.processingas confirmation; wait for the terminal event (pix.payout.confirmedorpix.payout.failed).
Common fields
All webhook payloads include these fields:
| Field | Type | Description |
|---|---|---|
event_type | string | Type of event that triggered the webhook (e.g., pix.charge.paid) |
status | string | Operation status — see Status Reference |
account_id | integer | Your account number at Owem |
entity_id | string (UUID) | Owem entity identifier |
Monetary values: All values are in subcentavos (1 BRL = 10,000 subcentavos). To convert to BRL: value / 10000. Example: 300000 / 10000 = R$ 30.00.
pix.charge.paid
Sent when a PIX is received and settled in the account. This is the event that confirms the money came in.
Example — linked to QR code
{
"event_type": "pix.charge.paid",
"status": "paid",
"account_id": 10014,
"amount": 300000,
"fee_amount": 400,
"end_to_end_id": "E9040088820260402095758709999671",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"tx_id": "u5f26sfyrq4plkw7tjwa",
"qr_code_id": "f401d5e3-a2b1-4c8e-9f3d-1234567890ab",
"counterparty_name": "MARIA SANTOS",
"payer_document": "12345678901",
"payer_ispb": "60701190",
"payer_bank_name": "Itau Unibanco S.A.",
"external_id": "order-9876",
"paid_at": "2026-04-02T09:58:05Z",
"recipient_key": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"recipient_key_type": "evp",
"receiver": {
"name": "PENHOTA GESTAO E INTERMEDIACAO LTDA",
"document": "62188010000150",
"account": "0000000019",
"ispb": "37839059",
"institution_name": "OWEM PAY IP"
}
}Example — direct transfer (no QR)
{
"event_type": "pix.charge.paid",
"status": "paid",
"account_id": 10014,
"amount": 300000,
"fee_amount": 400,
"end_to_end_id": "E9040088820260402095758709999671",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"tx_id": null,
"qr_code_id": null,
"counterparty_name": "JOAO SILVA",
"payer_document": "98765432100",
"payer_ispb": "00000000",
"payer_bank_name": "Banco do Brasil S.A.",
"external_id": null,
"paid_at": "2026-04-02T10:15:22Z",
"recipient_key": "12345678901",
"recipient_key_type": "cpf",
"receiver": {
"name": "PENHOTA GESTAO E INTERMEDIACAO LTDA",
"document": "62188010000150",
"account": "0000000019",
"ispb": "37839059",
"institution_name": "OWEM PAY IP"
}
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.charge.paid |
status | string | Always paid |
account_id | integer | Account number that received the PIX |
amount | integer | Amount received in subcentavos. 300000 = R$ 30.00 |
fee_amount | integer | Fee charged in subcentavos. 400 = R$ 0.04 |
end_to_end_id | string | BACEN E2E identifier (unique per PIX transaction) |
entity_id | string (UUID) | Owem entity identifier |
tx_id | string or null | Transaction ID. Present when linked to a QR code. null for direct transfers |
qr_code_id | string or null | UUID of the linked QR code. null for direct transfers |
counterparty_name | string or null | Name of the payer (sender) |
payer_document | string or null | Payer's CPF/CNPJ (digits only) |
payer_ispb | string or null | ISPB (8 digits) of the payer's institution |
payer_bank_name | string or null | Payer's institution name, resolved via BCB cache (896 banks) |
external_id | string or null | Your external identifier. Present when the QR code was created via API with external_id. null for direct transfers or QR without external_id |
paid_at | string (ISO 8601) | Settlement date/time (UTC) |
recipient_key | string or null | PIX key that received the payment (EVP, CPF, CNPJ, email, or phone) |
recipient_key_type | string or null | Recipient PIX key type: evp, phone, email, cpf, cnpj |
receiver | object | Full recipient data (you). Includes name, document, account, ispb, institution_name |
Payload variation: post-deploy reconciliation
In rare scenarios (backend pod killed before the webhook was sent, retroactive replay after an incident), the PostDeployReconciliation worker may dispatch pix.charge.paid with reduced fields — typically without receiver, payer_ispb, payer_bank_name, recipient_key or recipient_key_type. Fields that are always present: event_type, status, account_id, amount, end_to_end_id, fee_amount, counterparty_name, payer_document, external_id, paid_at, tx_id (when linked to a QR).
Your consumer should treat all non-mandatory fields as optional (nil/absent) and reconcile by end_to_end_id.
qr_code_id is a canonical UUID v4
The qr_code_id field is always serialized as UUID v4 in canonical form (36 characters with hyphens: f401d5e3-a2b1-4c8e-9f3d-1234567890ab) — never as raw binary, base64 or hex without hyphens. Use for direct correlation with the POST /api/external/pix/cash-in response (the transaction_id in your request returns the QR tx_id, and qr_code_id here is the internal primary key).
pix.charge.expired
Dispatched automatically when a QR code expires without payment. Executed by the QrExpirationChecker worker with cron */5 * * * * (every 5 minutes, over QR codes whose expires_at has passed).
{
"event_type": "pix.charge.expired",
"status": "expired",
"account_id": 10014,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"tx_id": "abc123def456ghi789",
"amount": 500000,
"external_id": "order-9876",
"expired_at": "2026-04-02T14:30:00Z"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.charge.expired |
status | string | Always expired |
account_id | integer | Account that issued the QR code |
entity_id | string (UUID) | Owem entity identifier |
tx_id | string | Charge/QR code ID |
amount | integer | Expected amount in subcentavos (not charged) |
external_id | string or null | Your external identifier, if provided at creation |
expired_at | string (ISO 8601) | Moment when the worker detected expiration (UTC) — may be later than the QR's actual expires_at by up to ~5 min |
pix.charge.cancelled
Sent when a QR code is explicitly cancelled by the merchant before it is paid, via action on the portal (backend: Fluxiq.UseCases.Pix.QRCodes.cancel_qrcode/2). Not dispatched on automatic expiration (use pix.charge.expired) or on payment (pix.charge.paid).
{
"event_type": "pix.charge.cancelled",
"status": "cancelled",
"tx_id": "abc123def456ghi789",
"amount": 500000,
"account_id": 10014,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"external_id": "order-9876",
"cancelled_at": "2026-04-23T12:30:00Z"
}| Field | Type | Description |
|---|---|---|
tx_id | string | Charge/QR code ID |
amount | integer | Expected amount in subcentavos (not charged) |
external_id | string or null | Your external identifier, if provided at creation |
cancelled_at | string (ISO 8601) | Moment when cancellation took effect (UTC) |
Distinction between cancelled, expired, and paid
pix.charge.cancelled: merchant intentionally cancelled before paymentpix.charge.expired: QR lifetime expired (workerQrExpirationCheckerevery 5 min)pix.charge.paid: charge settled successfully
pix.charge.created
Sent when a QR code is generated or a cash-in is initiated. No financial movement occurred.
{
"event_type": "pix.charge.created",
"status": "created",
"account_id": 10014,
"amount": 500000,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"tx_id": "abc123def456ghi789",
"external_id": "order-9876"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.charge.created |
status | string | Always created |
amount | integer | Expected amount in subcentavos |
tx_id | string | Charge/QR code ID |
external_id | string or null | Your external identifier, returned as sent. null if not provided or QR generated via portal |
pix.payout.confirmed
Sent when a sent PIX is confirmed by the destination institution. Definitive debit.
{
"event_type": "pix.payout.confirmed",
"status": "settled",
"account_id": 10014,
"amount": 500000,
"fee_amount": 200,
"end_to_end_id": "E3783905920260402101500000001",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "payment-456",
"pix_key": "destinatario@email.com",
"pix_key_type": "EMAIL",
"description": "Pagamento fornecedor",
"initiated_at": "2026-04-02T10:14:59Z",
"recipient": {
"name": "EMPRESA DESTINO LTDA",
"document": "12345678000199",
"ispb": "60701190",
"account": "12345678",
"agency": "0001",
"institution_name": "Itau Unibanco S.A."
},
"sender": {
"name": "MINHA EMPRESA LTDA",
"document": "98765432000100",
"ispb": "37839059",
"account": "00001234",
"agency": "0001"
}
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.payout.confirmed |
status | string | Always settled — definitive debit |
amount | integer | Sent amount in subcentavos |
fee_amount | integer | Fee charged in subcentavos |
end_to_end_id | string | BACEN E2E identifier |
transaction_id | string (UUID) | Unique transaction identifier |
external_id | string or null | Your external identifier |
pix_key | string | Recipient's PIX key |
pix_key_type | string | Key type: CPF, CNPJ, EMAIL, PHONE, EVP |
description | string or null | Description provided by the sender |
initiated_at | string (ISO 8601) | Moment when this webhook was dispatched (UTC). Not the timestamp of the original cash-out request nor of the BACEN settlement. To correlate with the moment you sent the POST, use the created_at from GET /api/external/transactions/ref/{external_id}; for the exact webhook delivery moment, use the X-Owem-Timestamp header |
recipient | object | Recipient's bank data (resolved via DICT) |
recipient.name | string or null | Destination account holder name |
recipient.document | string or null | Recipient's CPF/CNPJ (digits only) |
recipient.ispb | string or null | Destination institution ISPB |
recipient.account | string or null | Destination account number |
recipient.agency | string or null | Destination account agency |
recipient.institution_name | string or null | Destination institution name (resolved via BCB cache) |
sender | object | Sender account bank data (your Owem account) |
sender.name | string or null | Sender account holder name |
sender.document | string or null | Sender's CPF/CNPJ (digits only) |
sender.ispb | string or null | Owem Pay's ISPB (37839059) |
sender.account | string or null | Sender account number |
sender.agency | string or null | Sender account agency |
pix.payout.processing
Sent when a PIX send is being processed. Balance is on hold but not definitive. This event is optional — if you only want to be notified in the terminal state, ignore it and wait for pix.payout.confirmed or pix.payout.failed.
{
"event_type": "pix.payout.processing",
"status": "processing",
"account_id": 10014,
"amount": 500000,
"fee_amount": 200,
"end_to_end_id": "E3783905920260402101500000001",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "payment-456",
"pix_key": "destinatario@email.com",
"pix_key_type": "EMAIL",
"description": "Pagamento fornecedor",
"initiated_at": "2026-04-02T10:14:59Z",
"recipient": {
"name": "EMPRESA DESTINO LTDA",
"document": "12345678000199",
"ispb": "60701190",
"account": "12345678",
"agency": "0001",
"institution_name": "Itau Unibanco S.A."
},
"sender": {
"name": "MINHA EMPRESA LTDA",
"document": "98765432000100",
"ispb": "37839059",
"account": "00001234",
"agency": "0001"
}
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.payout.processing |
status | string | Always processing — balance on hold, may reverse |
amount | integer | Value in subcentavos |
fee_amount | integer | Fee in subcentavos (same fee that will appear in confirmed/failed later — it is computed at cash-out creation, not afterward) |
end_to_end_id | string | BACEN E2E identifier |
transaction_id | string (UUID) | Unique transaction identifier |
external_id | string or null | Your external identifier |
pix_key | string | Recipient's PIX key |
pix_key_type | string | Key type: CPF, CNPJ, EMAIL, PHONE, EVP |
description | string or null | Description provided by the sender |
initiated_at | string (ISO 8601) | Moment this webhook was dispatched (UTC) — see note in pix.payout.confirmed |
recipient | object | Recipient's bank data (resolved via DICT) |
recipient.name | string or null | Destination account holder name |
recipient.document | string or null | Recipient's CPF/CNPJ (digits only) |
recipient.ispb | string or null | Destination institution ISPB |
recipient.account | string or null | Destination account number |
recipient.agency | string or null | Destination account agency |
recipient.institution_name | string or null | Destination institution name |
sender | object | Sender account bank data (your Owem account) |
sender.name | string or null | Sender account holder name |
sender.document | string or null | Sender's CPF/CNPJ (digits only) |
sender.ispb | string or null | Owem Pay's ISPB (37839059) |
sender.account | string or null | Sender account number |
sender.agency | string or null | Sender account agency |
Event order
A pix.payout.processing is always followed (seconds to minutes later) by a pix.payout.confirmed or pix.payout.failed. In fast transactions (immediate settlement), processing may be omitted and you receive the terminal directly.
pix.payout.failed
Sent when a sent PIX is rejected. Hold released, balance restored.
Updated on 2026-04-10
The payload includes the structured fields reason_code (BACEN SPI code of 2–6 characters) and reason_description (English description). New integrations should use these fields for programmatic failure routing.
Mutual exclusion: when the backend can extract a BACEN code from the rejection (e.g., "rejected: AC03"), the payload sends only reason_code + reason_description — the legacy reason field is removed. When the failure has no parseable BACEN code (e.g., internal timeout, provider error without a code), the payload sends only reason (free string) — no reason_code. Handle both formats in your consumer.
{
"event_type": "pix.payout.failed",
"status": "rejected",
"account_id": 10014,
"amount": 500000,
"fee_amount": 200,
"end_to_end_id": "E3783905920260402101500000001",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "payment-456",
"pix_key": "destinatario@email.com",
"pix_key_type": "EMAIL",
"description": "Pagamento fornecedor",
"initiated_at": "2026-04-02T10:14:59Z",
"reason_code": "AC03",
"reason_description": "Invalid creditor account number",
"reason": "Conta destinatario nao encontrada",
"recipient": {
"name": "EMPRESA DESTINO LTDA",
"document": "12345678000199",
"ispb": "60701190",
"account": "12345678",
"agency": "0001",
"institution_name": "Itau Unibanco S.A."
},
"sender": {
"name": "MINHA EMPRESA LTDA",
"document": "98765432000100",
"ispb": "37839059",
"account": "00001234",
"agency": "0001"
}
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.payout.failed |
status | string | Always rejected — hold released, balance restored |
amount | integer | Value in subcentavos |
fee_amount | integer | Fee in subcentavos. The fee shown is what would have been charged — on the TB ledger the pending transfer is automatically reverted, so in practice there is no fee debit on rejected transactions |
end_to_end_id | string | BACEN E2E identifier |
transaction_id | string (UUID) | Unique transaction identifier |
external_id | string or null | Your external identifier |
pix_key | string | Recipient's PIX key |
pix_key_type | string | Key type: CPF, CNPJ, EMAIL, PHONE, EVP |
description | string or null | Description provided by the sender |
initiated_at | string (ISO 8601) | Moment this webhook was dispatched (UTC) |
reason_code | string or absent | Structured BACEN SPI code (2–6 characters). Examples: AC03, ED05, AM02, BE01, MD06, FOCR. Present when the backend extracted a BACEN code from the rejection. Use this field for programmatic routing |
reason_description | string or absent | English description of the reason_code. Present along with reason_code. Example: "Invalid creditor account number" |
reason | string or absent | [Legacy] Free-form description of the reason. Present only when the rejection has no parseable BACEN code — mutually exclusive with reason_code |
recipient | object | Recipient's bank data (resolved via DICT) |
recipient.name | string or null | Destination account holder name |
recipient.document | string or null | Recipient's CPF/CNPJ (digits only) |
recipient.ispb | string or null | Destination institution ISPB |
recipient.account | string or null | Destination account number |
recipient.agency | string or null | Destination account agency |
recipient.institution_name | string or null | Destination institution name |
sender | object | Sender account bank data (your Owem account) |
sender.name | string or null | Sender account holder name |
sender.document | string or null | Sender's CPF/CNPJ (digits only) |
sender.ispb | string or null | Owem Pay's ISPB (37839059) |
sender.account | string or null | Sender account number |
sender.agency | string or null | Sender account agency |
Payload variations across dispatch sites
pix.payout.failed is dispatched from multiple paths — the main one in pix.ex extracts the BACEN code from strings "rejected: <CODE>" and applies the reason vs reason_code mutual exclusion described above. Secondary paths (stale checker, retry worker, post-deploy reconciliation) may send both reason and reason_code in the same payload, or only reason without structure. Always treat both fields as optional and prefer reason_code when present.
Most common reason_code (BACEN SPI)
| Code | English meaning | Recommended action |
|---|---|---|
AC03 | Invalid creditor account number | Confirm recipient's bank data with the end customer |
AC06 | Creditor account blocked | Destination account blocked — do not retry |
AM02 | Not allowed amount (limit exceeded) | Amount exceeds destination's or source's PIX limit |
AM04 | Insufficient funds | Insufficient funds at the source |
BE01 | End customer not in whitelist | Recipient identifier not recognized |
ED05 | Settlement failed | Settlement failure — may retry after investigation |
MD06 | Refund requested by end customer | Refund requested by the end customer |
FOCR | Forbidden credit return | Credit return forbidden |
Full list: see the SPI Message Catalog by BACEN.
pix.payout.returned
Sent when a PIX you sent is returned by the destination bank after settlement. Rare, but can happen several days later. The merchant balance increases (inflow).
Naming distinction
Three different flows can be confused:
pix.return.received: a PIX you received is being returned to the original payer. Balance DECREASES.pix.payout.returned(this): a PIX you sent is coming back to you. Balance INCREASES.pix.refund.requested: MED preventive block on a PIX you received. Funds frozen.
Same payload, two events, two different statuses
The backend dispatches pix.return.received and pix.payout.returned in the same call (return_in_handler.ex) using the same base payload with the status field rewritten:
pix.return.received→status: "settled"(PIX you received is being returned → balance decreases)pix.payout.returned→status: "returned"(PIX you sent is coming back → balance increases)
If your reconciliation logic filters by status or dedupes by (e2e, event_type), make sure to distinguish event_type first — the payload is almost identical.
{
"event_type": "pix.payout.returned",
"status": "returned",
"account_id": 10014,
"amount": 500000,
"original_amount": 500000,
"refunded_amount": 500000,
"fee_amount": 0,
"net_amount": 500000,
"is_partial": false,
"total_refunded": 500000,
"remaining_refundable": 0,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"return_e2e_id": "D3783905920260410111500000001",
"end_to_end_id": "E3783905920260402101500000001",
"original_transaction_id": "PIXOUTa1b2c3d4e5f67890abcdef1234567890",
"external_id": "payment-456",
"return_reason": "MD06",
"return_reason_description": "Refund requested by end customer",
"counterparty_ispb": "60701190",
"counterparty_name": "EMPRESA DESTINO LTDA",
"counterparty_document": "12345678000199",
"counterparty_institution_name": "Itau Unibanco S.A.",
"returned_at": "2026-04-10T11:15:00Z"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.payout.returned |
status | string | Always returned — return settled and credited to your account |
amount | integer | Same value as refunded_amount (kept for compatibility) |
original_amount | integer | Value of the original PIX OUT in subcentavos |
refunded_amount | integer | Amount effectively returned in this return (may be partial) |
fee_amount | integer | Fee charged on this return (usually 0) |
net_amount | integer | refunded_amount - fee_amount |
is_partial | boolean | true when refunded_amount < original_amount or balance remains to be returned |
total_refunded | integer | Sum of all returns already received for this original transaction (includes this one) |
remaining_refundable | integer | max(original_amount - total_refunded, 0) — balance still refundable |
return_e2e_id | string | Return E2E (prefix D) |
end_to_end_id | string | E2E of the original PIX OUT (prefix E) |
original_transaction_id | string | transaction_id of the original PIX OUT. Use for correlation with your system |
external_id | string or null | Your external identifier from the original transaction (if applicable) |
return_reason | string | BACEN return code: MD06, BE08, FR01, SL02 |
return_reason_description | string | English description of return_reason |
counterparty_ispb | string | ISPB of the institution that initiated the return |
counterparty_name | string | Counterparty name (destination institution of the original PIX) |
counterparty_document | string or null | Counterparty's CPF/CNPJ |
counterparty_institution_name | string or null | Counterparty institution name (BCB cache) |
returned_at | string (ISO 8601) | Moment this webhook was dispatched (UTC) |
Fee is not reimbursed
The fee of the original cash-out is not reimbursed on pix.payout.returned. The fee was charged for the successful send that actually happened. If your business rule requires reimbursing the fee to the end customer, the merchant must do this separately.
pix.refund.requested
Sent when a PIX refund is requested via MED (Special Refund Mechanism). Funds were preventively blocked in the account of the merchant that received the original PIX.
PIX In only
This event applies only to received PIX (cash-in). If you sent a PIX and it was returned, you will receive pix.return.received instead of pix.refund.*.
{
"event_type": "pix.refund.requested",
"status": "requested",
"account_id": 10014,
"requested_amount": 300000,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"block_id": "b1c2d3e4-f5g6-7890-hijk-lm1234567890",
"infraction_report_id": "INF20260402001",
"e2e_id": "E9040088820260402095758709999671",
"external_id": null,
"blocked_amount": 300000,
"fee_amount": 0,
"fraud_category": "OTHER",
"deadline": "2026-04-09T14:30:00Z",
"scenario": "cautelar",
"created_at": "2026-04-02T14:30:00Z"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.refund.requested |
status | string | Always requested — preventive block active |
requested_amount | integer | Amount requested for refund in subcentavos |
block_id | string (UUID) | Preventive block identifier |
infraction_report_id | string | Infraction identifier at OnZ |
e2e_id | string | E2E of the original PIX transaction being disputed |
external_id | string or null | Your external identifier (if applicable) |
blocked_amount | integer | Amount effectively blocked in subcentavos |
fee_amount | integer | MED fee in subcentavos |
fraud_category | string | Category of alleged fraud. Possible values: SCAM, ACCOUNT_TAKEOVER, COERCION, FRAUDULENT_ACCESS, OTHER. When the counterparty does not send a specific FraudType, the value is OTHER (default for REFUND_REQUEST). |
deadline | string (ISO 8601) | Deadline for analysis/defense (UTC) |
scenario | string | MED scenario: cautelar or fraude |
created_at | string (ISO 8601) | Block date/time (UTC) |
pix.refund.completed
Dispatched when a MED refund is successfully executed. Dispatched via med/processor.ex:915 during the accepted MED cycle.
Payload format (confirmed by code path med/processor.ex:900-920):
{
"event_type": "pix.refund.completed",
"status": "settled",
"account_id": 10014,
"amount": 300000,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"block_id": "b1c2d3e4-f5g6-7890-hijk-lm1234567890",
"infraction_report_id": "INF20260402001",
"e2e_id": "E9040088820260402095758709999671",
"external_id": null,
"reason": "analysis_unfounded",
"completed_at": "2026-04-02T14:30:00Z"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.refund.completed |
status | string | Always completed — MED refund finalized |
amount | integer | Amount refunded in subcentavos |
block_id | string (UUID) | Preventive block identifier |
infraction_report_id | string | OnZ infraction identifier |
e2e_id | string | E2E of the original PIX transaction |
external_id | string or null | Your external identifier (if applicable) |
reason | string | Release reason (e.g., analysis_unfounded, manual_release) |
completed_at | string (ISO 8601) | Completion date/time (UTC) |
pix.return.received
Sent when a PIX return is received. This event is generated when a PIX that you previously received (cash-in) is being returned to the original payer. The merchant balance decreases.
Naming distinction
pix.return.received(this): a PIX you received is being returned to the original payer. Balance DECREASES.pix.payout.returned: a PIX you sent is coming back to you. Balance INCREASES.
The names are inverse to what the plain meaning suggests — pay attention.
{
"event_type": "pix.return.received",
"status": "settled",
"account_id": 10014,
"amount": 300000,
"original_amount": 300000,
"refunded_amount": 300000,
"fee_amount": 0,
"net_amount": 300000,
"is_partial": false,
"total_refunded": 300000,
"remaining_refundable": 0,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"return_e2e_id": "D9040088820260402111500000001",
"end_to_end_id": "E9040088820260402095758709999671",
"original_transaction_id": "PIXINE9040088820260402095758709999671",
"external_id": null,
"return_reason": "MD06",
"return_reason_description": "Refund requested by end customer",
"counterparty_ispb": "60701190",
"counterparty_name": "EMPRESA X LTDA",
"counterparty_document": "98765432100",
"counterparty_institution_name": "Itau Unibanco S.A.",
"returned_at": "2026-04-02T11:15:00Z"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.return.received |
status | string | Always settled — return settled |
amount | integer | Same value as refunded_amount (kept for compatibility) |
original_amount | integer | Value of the original PIX IN in subcentavos |
refunded_amount | integer | Amount effectively returned in this return (may be partial) |
fee_amount | integer | Fee charged on this return |
net_amount | integer | refunded_amount - fee_amount |
is_partial | boolean | true when the return does not cover the original PIX IN's full amount |
total_refunded | integer | Sum of all returns already sent for this transaction (includes this one) |
remaining_refundable | integer | Balance still refundable |
return_e2e_id | string | Return E2E (prefix D) |
end_to_end_id | string | E2E of the original PIX IN (prefix E) |
original_transaction_id | string | transaction_id of the original PIX IN. Use for correlation with your system |
external_id | string or null | Your external identifier from the original transaction (if applicable) |
return_reason | string | BACEN return code: MD06, BE08, FR01, SL02 |
return_reason_description | string | English description of return_reason |
counterparty_ispb | string | ISPB of the institution receiving the return |
counterparty_name | string | Counterparty name (original PIX IN payer) |
counterparty_document | string or null | Counterparty's CPF/CNPJ |
counterparty_institution_name | string or null | Counterparty institution name (BCB cache) |
returned_at | string (ISO 8601) | Moment this webhook was dispatched (UTC) |
Deduplication
To deduplicate webhook retries, use the X-Owem-Event-Id header OR the combination (return_e2e_id, end_to_end_id). The return_e2e_id starts with D (return) and the end_to_end_id starts with E (original).
webhook.test
Test event dispatched manually to validate webhook configuration.
{
"event_type": "webhook.test",
"status": "test",
"account_id": 10014,
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7",
"message": "Webhook test event"
}pix.infraction.created
Dispatched when a PIX infraction is reported by the counterparty (via BACEN DICT). Requires defense by defense_deadline OR automatic preventive block (MED).
Dispatched by pix_compliance.ex:463 (function upsert_infraction when is_new=true).
{
"event_type": "pix.infraction.created",
"infraction_id": "e7f4d23a-6f2a-4d1e-a3e6-fe8b32bba95d",
"e2e_id": "E0416201020260404113012abcdef1234",
"status": "ACKNOWLEDGED",
"infraction_type": "REFUND_REQUEST",
"amount": 1500000,
"analysis_result": null,
"analysis_details": null,
"defense_deadline": "2026-04-21T23:59:59Z",
"counterpart_ispb": "60701190",
"account_id": 10011,
"merchant_id": "1b2db911-972f-4466-9be9-60a7c5450064",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.infraction.created |
infraction_id | string (UUID) | Internal infraction ID |
e2e_id | string | E2E of the disputed transaction |
status | string | BACEN status: ACKNOWLEDGED, CLOSED, CANCELLED |
infraction_type | string | BACEN type: REFUND_REQUEST, REFUND_CANCELLED, FRAUD |
amount | integer | Value in subcentavos |
defense_deadline | string (ISO 8601) | Deadline for defense submission |
counterpart_ispb | string (8 digits) | Counterparty institution ISPB |
account_id | integer | Your affected account |
merchant_id | string (UUID) | Your merchant_id |
Mandatory action
Infractions with status ACKNOWLEDGED and value > R$1,000 generate an automatic preventive block (MED). You must respond via POST /api/merchant/infractions/{id}/defense before the defense_deadline or the refund will be executed.
pix.infraction.resolved
Dispatched when an infraction is resolved (admin close or auto-deny). Releases the preventive block if any.
Dispatched by pix_compliance.ex:664 and admin/infractions/resolve_controller.ex:84.
{
"event_type": "pix.infraction.resolved",
"infraction_id": "e7f4d23a-6f2a-4d1e-a3e6-fe8b32bba95d",
"e2e_id": "E0416201020260404113012abcdef1234",
"status": "CLOSED",
"infraction_type": "REFUND_REQUEST",
"amount": 1500000,
"analysis_result": "DISAGREED",
"analysis_details": "Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao",
"defense_deadline": "2026-04-21T23:59:59Z",
"counterpart_ispb": "60701190",
"account_id": 10011,
"merchant_id": "1b2db911-972f-4466-9be9-60a7c5450064",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7"
}| Field | Type | Description |
|---|---|---|
analysis_result | string | AGREED (refund), DISAGREED (deny) |
analysis_details | string | Decision justification |
| Other fields | Identical to pix.infraction.created |
pix.infraction.defense_submitted
Dispatched when the merchant submits a defense against an infraction (via portal or API).
Dispatched by admin/infractions/defense_controller.ex:67 and merchant/infractions/defense_controller.ex:178.
{
"event_type": "pix.infraction.defense_submitted",
"infraction_id": "e7f4d23a-6f2a-4d1e-a3e6-fe8b32bba95d",
"e2e_id": "E0416201020260404113012abcdef1234",
"status": "defense_submitted",
"account_id": 10011,
"merchant_id": "1b2db911-972f-4466-9be9-60a7c5450064",
"entity_id": "26a48541-edce-4581-8c6e-564e7f2e6cd7"
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.infraction.defense_submitted |
status | string | Always defense_submitted |
infraction_id | string (UUID) | ID of the infraction being defended |
Awaits BACEN analysis
After submission, BACEN analyzes the defense + counterparty evidence. Result via pix.infraction.resolved.
pix.payout.queued
Dispatched when a PIX OUT is automatically placed in the retry queue (feature pix_out_retry_queue_enabled, available since session 155). Reasons: per-merchant ClientLimiter rate limit or exhaustion of the shared BACEN DICT bucket.
Dispatched by pix.ex:315 (module Fluxiq.UseCases.Payments.OutboundPayment.Pix).
{
"event_type": "pix.payout.queued",
"status": "queued",
"account_id": 10011,
"merchant_id": "1b2db911-972f-4466-9be9-60a7c5450064",
"transaction_id": "PIXOUT0200806193e0984f830569",
"end_to_end_id": "E3783905920260421133012abcdef1234",
"amount": 200,
"external_id": "payment-456",
"reason": "dict_client_rate_limited",
"reason_code": "DICT_CLIENT_RATE_LIMITED",
"reason_description": "Merchant exceeded per-minute DICT lookup quota",
"queued_at": "2026-04-21T13:30:12Z",
"estimated_retry_seconds": 3,
"queue_ttl_seconds": 7200
}| Field | Type | Description |
|---|---|---|
event_type | string | Always pix.payout.queued |
status | string | Always queued |
account_id | integer | Account that originated the PIX OUT |
merchant_id | string (UUID) | Your merchant_id |
transaction_id | string | Owem transaction identifier |
end_to_end_id | string | BACEN E2E generated for the PIX OUT |
amount | integer | Value in subcentavos |
external_id | string or null | Your external identifier (if sent in the original request) |
reason | string | Queue reason (snake_case). Known values: dict_client_rate_limited (per-merchant limit), dict_bucket_exhausted (shared BACEN DICT bucket exhausted), dict_rate_limited (generic fallback) |
reason_code | string | Internal UPPERCASE code correlated with reason. Values: DICT_CLIENT_RATE_LIMITED, DICT_BUCKET_EXHAUSTED, DICT_RATE_LIMITED. Not a BACEN SPI code (like AC03, AM02) — the queueing happens before sending to BACEN, so the codes are internal to Owem |
reason_description | string | English description of the reason |
queued_at | string (ISO 8601) | Moment it entered the queue (UTC) |
estimated_retry_seconds | integer | Retry interval of the worker (default 3 s); the queue does not guarantee 3 s — it may take longer if the bucket takes time to free tokens |
queue_ttl_seconds | integer | Maximum TTL in the queue in seconds (7200 = 2 h). After expiration, the request goes to failed with reason queue_ttl_expired |
reason_code here is not BACEN SPI
Note that in pix.payout.queued the reason_code is an Owem internal UPPERCASE code (DICT_CLIENT_RATE_LIMITED, etc.). In pix.payout.failed the reason_code is a BACEN SPI code (e.g., AC03, AM02, ED05). The two fields share the same name but have different vocabularies — handle each event separately in your consumer.
Automatic drain
Queued requests are retried automatically by the worker every ~3s while TTL remains. Under normal conditions processing resumes as soon as the per-merchant limit or BACEN DICT bucket has capacity, but this is not a 3–10 min SLA. Next event: pix.payout.processing (when it leaves the queue and is sent to BACEN). If the 2-hour TTL expires without success, you receive pix.payout.failed with reason="queue_ttl_expired".
How to interpret webhooks
To confirm money came into the account: Wait for pix.charge.paid with status: "paid". This is the only event that guarantees the value was credited and the fee was charged.
To confirm money left the account: Wait for pix.payout.confirmed with status: "settled". The processing status is intermediate — the balance is reserved but can be reverted if rejected.
For returns: pix.return.received with status: "settled" confirms that a return was settled and credited to the account.
Deduplication: Use the X-Owem-Event-Id header or the end_to_end_id field as an idempotency key.