Skip to content

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.

EventStatusMeaningMoney settled?
pix.charge.createdcreatedQR code generated or cash-in initiated. Awaiting payment.No — only created
pix.charge.paidpaidPIX received and settled in the account. Balance updated, fee charged.Yes
pix.charge.expiredexpiredQR code expired without payment.N/A
pix.charge.cancelledcancelledQR code explicitly cancelled by the merchant before payment.N/A
pix.payout.queuedqueuedPIX send queued by rate limit (ClientLimiter or DICT bucket). No debit yet.No — awaiting quota
pix.payout.processingprocessingPIX sent, awaiting destination confirmation. Balance on hold.No — may reverse
pix.payout.confirmedsettledPIX sent and confirmed by the destination. Definitive debit.Yes
pix.payout.failedrejectedPIX send rejected by the destination. Hold released, balance restored.No
pix.payout.returnedreturnedSent PIX was returned after settlement.Yes (reversal)
pix.refund.requestedrequestedPIX refund requested (MED). Preventive block created.Partial
pix.refund.completedsettled / completedPIX refund completed and settled. Definitive debit.Yes
pix.return.receivedsettledPIX return received and settled (credit into the account).Yes
pix.infraction.createdACKNOWLEDGEDPIX infraction reported against you. Requires action.Partial — preventive block if >R$1k
pix.infraction.resolvedCLOSED / CANCELLEDInfraction resolved (refund executed or denied).N/A — effect on another event
pix.infraction.defense_submitteddefense_submittedDefense submitted by the merchant. Awaiting BACEN.N/A
webhook.testtestTest event dispatched manually via Admin/Merchant portal.N/A

Reconciliation rules:

  • Consider balance inflows only on the statuses: paid (PIX IN credit) and returned (reversal of a previously sent PIX OUT).
  • Consider balance outflows only on the statuses: settled (confirmed PIX OUT debit) and completed (final MED refund debit), and settled on pix.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.processing as confirmation; wait for the terminal event (pix.payout.confirmed or pix.payout.failed).

Common fields

All webhook payloads include these fields:

FieldTypeDescription
event_typestringType of event that triggered the webhook (e.g., pix.charge.paid)
statusstringOperation status — see Status Reference
account_idintegerYour account number at Owem
entity_idstring (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

json
{
  "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)

json
{
  "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"
  }
}
FieldTypeDescription
event_typestringAlways pix.charge.paid
statusstringAlways paid
account_idintegerAccount number that received the PIX
amountintegerAmount received in subcentavos. 300000 = R$ 30.00
fee_amountintegerFee charged in subcentavos. 400 = R$ 0.04
end_to_end_idstringBACEN E2E identifier (unique per PIX transaction)
entity_idstring (UUID)Owem entity identifier
tx_idstring or nullTransaction ID. Present when linked to a QR code. null for direct transfers
qr_code_idstring or nullUUID of the linked QR code. null for direct transfers
counterparty_namestring or nullName of the payer (sender)
payer_documentstring or nullPayer's CPF/CNPJ (digits only)
payer_ispbstring or nullISPB (8 digits) of the payer's institution
payer_bank_namestring or nullPayer's institution name, resolved via BCB cache (896 banks)
external_idstring or nullYour external identifier. Present when the QR code was created via API with external_id. null for direct transfers or QR without external_id
paid_atstring (ISO 8601)Settlement date/time (UTC)
recipient_keystring or nullPIX key that received the payment (EVP, CPF, CNPJ, email, or phone)
recipient_key_typestring or nullRecipient PIX key type: evp, phone, email, cpf, cnpj
receiverobjectFull 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).

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.charge.expired
statusstringAlways expired
account_idintegerAccount that issued the QR code
entity_idstring (UUID)Owem entity identifier
tx_idstringCharge/QR code ID
amountintegerExpected amount in subcentavos (not charged)
external_idstring or nullYour external identifier, if provided at creation
expired_atstring (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).

json
{
  "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"
}
FieldTypeDescription
tx_idstringCharge/QR code ID
amountintegerExpected amount in subcentavos (not charged)
external_idstring or nullYour external identifier, if provided at creation
cancelled_atstring (ISO 8601)Moment when cancellation took effect (UTC)

Distinction between cancelled, expired, and paid

  • pix.charge.cancelled: merchant intentionally cancelled before payment
  • pix.charge.expired: QR lifetime expired (worker QrExpirationChecker every 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.

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.charge.created
statusstringAlways created
amountintegerExpected amount in subcentavos
tx_idstringCharge/QR code ID
external_idstring or nullYour 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.

json
{
  "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"
  }
}
FieldTypeDescription
event_typestringAlways pix.payout.confirmed
statusstringAlways settled — definitive debit
amountintegerSent amount in subcentavos
fee_amountintegerFee charged in subcentavos
end_to_end_idstringBACEN E2E identifier
transaction_idstring (UUID)Unique transaction identifier
external_idstring or nullYour external identifier
pix_keystringRecipient's PIX key
pix_key_typestringKey type: CPF, CNPJ, EMAIL, PHONE, EVP
descriptionstring or nullDescription provided by the sender
initiated_atstring (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
recipientobjectRecipient's bank data (resolved via DICT)
recipient.namestring or nullDestination account holder name
recipient.documentstring or nullRecipient's CPF/CNPJ (digits only)
recipient.ispbstring or nullDestination institution ISPB
recipient.accountstring or nullDestination account number
recipient.agencystring or nullDestination account agency
recipient.institution_namestring or nullDestination institution name (resolved via BCB cache)
senderobjectSender account bank data (your Owem account)
sender.namestring or nullSender account holder name
sender.documentstring or nullSender's CPF/CNPJ (digits only)
sender.ispbstring or nullOwem Pay's ISPB (37839059)
sender.accountstring or nullSender account number
sender.agencystring or nullSender 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.

json
{
  "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"
  }
}
FieldTypeDescription
event_typestringAlways pix.payout.processing
statusstringAlways processing — balance on hold, may reverse
amountintegerValue in subcentavos
fee_amountintegerFee in subcentavos (same fee that will appear in confirmed/failed later — it is computed at cash-out creation, not afterward)
end_to_end_idstringBACEN E2E identifier
transaction_idstring (UUID)Unique transaction identifier
external_idstring or nullYour external identifier
pix_keystringRecipient's PIX key
pix_key_typestringKey type: CPF, CNPJ, EMAIL, PHONE, EVP
descriptionstring or nullDescription provided by the sender
initiated_atstring (ISO 8601)Moment this webhook was dispatched (UTC) — see note in pix.payout.confirmed
recipientobjectRecipient's bank data (resolved via DICT)
recipient.namestring or nullDestination account holder name
recipient.documentstring or nullRecipient's CPF/CNPJ (digits only)
recipient.ispbstring or nullDestination institution ISPB
recipient.accountstring or nullDestination account number
recipient.agencystring or nullDestination account agency
recipient.institution_namestring or nullDestination institution name
senderobjectSender account bank data (your Owem account)
sender.namestring or nullSender account holder name
sender.documentstring or nullSender's CPF/CNPJ (digits only)
sender.ispbstring or nullOwem Pay's ISPB (37839059)
sender.accountstring or nullSender account number
sender.agencystring or nullSender 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.

json
{
  "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"
  }
}
FieldTypeDescription
event_typestringAlways pix.payout.failed
statusstringAlways rejected — hold released, balance restored
amountintegerValue in subcentavos
fee_amountintegerFee 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_idstringBACEN E2E identifier
transaction_idstring (UUID)Unique transaction identifier
external_idstring or nullYour external identifier
pix_keystringRecipient's PIX key
pix_key_typestringKey type: CPF, CNPJ, EMAIL, PHONE, EVP
descriptionstring or nullDescription provided by the sender
initiated_atstring (ISO 8601)Moment this webhook was dispatched (UTC)
reason_codestring or absentStructured 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_descriptionstring or absentEnglish description of the reason_code. Present along with reason_code. Example: "Invalid creditor account number"
reasonstring or absent[Legacy] Free-form description of the reason. Present only when the rejection has no parseable BACEN code — mutually exclusive with reason_code
recipientobjectRecipient's bank data (resolved via DICT)
recipient.namestring or nullDestination account holder name
recipient.documentstring or nullRecipient's CPF/CNPJ (digits only)
recipient.ispbstring or nullDestination institution ISPB
recipient.accountstring or nullDestination account number
recipient.agencystring or nullDestination account agency
recipient.institution_namestring or nullDestination institution name
senderobjectSender account bank data (your Owem account)
sender.namestring or nullSender account holder name
sender.documentstring or nullSender's CPF/CNPJ (digits only)
sender.ispbstring or nullOwem Pay's ISPB (37839059)
sender.accountstring or nullSender account number
sender.agencystring or nullSender 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)

CodeEnglish meaningRecommended action
AC03Invalid creditor account numberConfirm recipient's bank data with the end customer
AC06Creditor account blockedDestination account blocked — do not retry
AM02Not allowed amount (limit exceeded)Amount exceeds destination's or source's PIX limit
AM04Insufficient fundsInsufficient funds at the source
BE01End customer not in whitelistRecipient identifier not recognized
ED05Settlement failedSettlement failure — may retry after investigation
MD06Refund requested by end customerRefund requested by the end customer
FOCRForbidden credit returnCredit 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.receivedstatus: "settled" (PIX you received is being returned → balance decreases)
  • pix.payout.returnedstatus: "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.

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.payout.returned
statusstringAlways returned — return settled and credited to your account
amountintegerSame value as refunded_amount (kept for compatibility)
original_amountintegerValue of the original PIX OUT in subcentavos
refunded_amountintegerAmount effectively returned in this return (may be partial)
fee_amountintegerFee charged on this return (usually 0)
net_amountintegerrefunded_amount - fee_amount
is_partialbooleantrue when refunded_amount < original_amount or balance remains to be returned
total_refundedintegerSum of all returns already received for this original transaction (includes this one)
remaining_refundableintegermax(original_amount - total_refunded, 0) — balance still refundable
return_e2e_idstringReturn E2E (prefix D)
end_to_end_idstringE2E of the original PIX OUT (prefix E)
original_transaction_idstringtransaction_id of the original PIX OUT. Use for correlation with your system
external_idstring or nullYour external identifier from the original transaction (if applicable)
return_reasonstringBACEN return code: MD06, BE08, FR01, SL02
return_reason_descriptionstringEnglish description of return_reason
counterparty_ispbstringISPB of the institution that initiated the return
counterparty_namestringCounterparty name (destination institution of the original PIX)
counterparty_documentstring or nullCounterparty's CPF/CNPJ
counterparty_institution_namestring or nullCounterparty institution name (BCB cache)
returned_atstring (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.*.

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.refund.requested
statusstringAlways requested — preventive block active
requested_amountintegerAmount requested for refund in subcentavos
block_idstring (UUID)Preventive block identifier
infraction_report_idstringInfraction identifier at OnZ
e2e_idstringE2E of the original PIX transaction being disputed
external_idstring or nullYour external identifier (if applicable)
blocked_amountintegerAmount effectively blocked in subcentavos
fee_amountintegerMED fee in subcentavos
fraud_categorystringCategory 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).
deadlinestring (ISO 8601)Deadline for analysis/defense (UTC)
scenariostringMED scenario: cautelar or fraude
created_atstring (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):

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.refund.completed
statusstringAlways completed — MED refund finalized
amountintegerAmount refunded in subcentavos
block_idstring (UUID)Preventive block identifier
infraction_report_idstringOnZ infraction identifier
e2e_idstringE2E of the original PIX transaction
external_idstring or nullYour external identifier (if applicable)
reasonstringRelease reason (e.g., analysis_unfounded, manual_release)
completed_atstring (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.

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.return.received
statusstringAlways settled — return settled
amountintegerSame value as refunded_amount (kept for compatibility)
original_amountintegerValue of the original PIX IN in subcentavos
refunded_amountintegerAmount effectively returned in this return (may be partial)
fee_amountintegerFee charged on this return
net_amountintegerrefunded_amount - fee_amount
is_partialbooleantrue when the return does not cover the original PIX IN's full amount
total_refundedintegerSum of all returns already sent for this transaction (includes this one)
remaining_refundableintegerBalance still refundable
return_e2e_idstringReturn E2E (prefix D)
end_to_end_idstringE2E of the original PIX IN (prefix E)
original_transaction_idstringtransaction_id of the original PIX IN. Use for correlation with your system
external_idstring or nullYour external identifier from the original transaction (if applicable)
return_reasonstringBACEN return code: MD06, BE08, FR01, SL02
return_reason_descriptionstringEnglish description of return_reason
counterparty_ispbstringISPB of the institution receiving the return
counterparty_namestringCounterparty name (original PIX IN payer)
counterparty_documentstring or nullCounterparty's CPF/CNPJ
counterparty_institution_namestring or nullCounterparty institution name (BCB cache)
returned_atstring (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.

json
{
  "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).

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.infraction.created
infraction_idstring (UUID)Internal infraction ID
e2e_idstringE2E of the disputed transaction
statusstringBACEN status: ACKNOWLEDGED, CLOSED, CANCELLED
infraction_typestringBACEN type: REFUND_REQUEST, REFUND_CANCELLED, FRAUD
amountintegerValue in subcentavos
defense_deadlinestring (ISO 8601)Deadline for defense submission
counterpart_ispbstring (8 digits)Counterparty institution ISPB
account_idintegerYour affected account
merchant_idstring (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.

json
{
  "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"
}
FieldTypeDescription
analysis_resultstringAGREED (refund), DISAGREED (deny)
analysis_detailsstringDecision justification
Other fieldsIdentical 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.

json
{
  "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"
}
FieldTypeDescription
event_typestringAlways pix.infraction.defense_submitted
statusstringAlways defense_submitted
infraction_idstring (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).

json
{
  "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
}
FieldTypeDescription
event_typestringAlways pix.payout.queued
statusstringAlways queued
account_idintegerAccount that originated the PIX OUT
merchant_idstring (UUID)Your merchant_id
transaction_idstringOwem transaction identifier
end_to_end_idstringBACEN E2E generated for the PIX OUT
amountintegerValue in subcentavos
external_idstring or nullYour external identifier (if sent in the original request)
reasonstringQueue 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_codestringInternal 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_descriptionstringEnglish description of the reason
queued_atstring (ISO 8601)Moment it entered the queue (UTC)
estimated_retry_secondsintegerRetry 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_secondsintegerMaximum 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.

Owem Pay Instituição de Pagamento — ISPB 37839059