PIX Refund
Initiates a refund (total or partial) of a received PIX transaction.
Endpoint
POST /api/external/pix/refundHeaders
| Header | Type | Required | Description |
|---|---|---|---|
Authorization | String | Yes | ApiKey {client_id}:{client_secret} |
Content-Type | String | Yes | application/json |
hmac | String | Yes | HMAC-SHA512 signature of the body (learn more) |
Idempotency-Key | String | No | Unique key to prevent duplicate processing (max 256 chars) |
X-Key-Case | String | No | Set to camelCase to receive response fields in camelCase (default is snake_case) |
Request Body
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
original_transaction_id | String | Yes | ID of the original received PIX transaction (see aliases below) | "7popu57v6us7p6pcicgq12345" |
amount | Integer | No | Amount to refund in centavos in the request. If omitted, refunds the original transaction's full amount. | 3000 (R$ 30.00) |
reason | String | No | BACEN refund code (see table below). Default: MD06. Not validated locally — sent straight to BACEN. | "MD06" |
description | String | No | Refund description. Used as return_reason at BACEN (up to 140 characters). Default: "Devolução PIX". | "Devolução solicitada pelo cliente" |
Accepted aliases
For integration flexibility, the backend accepts multiple names for each field (first match wins):
original_transaction_id(canonical)original_e2e_id— accepts the original transaction's E2E ID (prefixE)originalTransactionId— camelCasetransaction_id— fallbackend_to_end_id— fallback
reason(canonical)return_code— direct alias
Ownership validation
The backend validates that the original transaction belongs to the same merchant of the API Key calling the endpoint. If you try to refund a transaction from another merchant, you get HTTP 404 "original transaction not found" — same response as a nonexistent transaction (for security).
Monetary values (request vs response)
The amount in the request is in centavos (R$ 1.00 = 100). The amount in the response and webhooks is in subcentavos / base units (R$ 1.00 = 10000). Example: send 3000 to refund R$ 30.00; the response returns 300000.
NEVER send float/decimal. Always send an integer in centavos in the request and divide response values by 10,000 for BRL display.
Partial refund -- tracking the remaining amount
For a partial refund, provide an amount less than the original value. The total of refunds for a single transaction cannot exceed the original amount received.
The backend tracks the accumulated in total_refunded and remaining_refundable in the pix.refund.completed and pix.return.received webhooks:
total_refunded= sum of all refunds already executed for that originalend_to_end_idremaining_refundable=original_amount - total_refunded(how much can still be refunded)is_partial=trueif this refund was partial
Before making a second partial refund, check the last pix.refund.completed webhook to know the remaining_refundable — if you exceed it, the backend returns HTTP 422 "Valor da devolução excede o valor original da transação".
Refund Codes
| Code | Description |
|---|---|
MD06 | Refund by agreement between parties |
BE08 | Fraud |
AM09 | Incorrect amount |
SL02 | Settlement error |
RR04 | Unrecognized transaction (BACEN) |
FR01 | Fraud reported on the recipient side (BACEN code observed in MED flows) |
Reason code validation
Owem does not validate the reason field locally. The code is forwarded to BACEN via PACS.004.
- Valid BACEN codes for PACS.004 refund:
MD06,BE08,AM09,SL02,RR04,FR01. - Friendly descriptions in the
pix.return.receivedwebhook: the backend maintains a local mapping (return_reasons) forMD06,BE08,FR01,SL02. Codes outside this mapping (e.g.,RR04,AM09) fall back to the generic description"Return from counterparty ({code})"in the webhook.
The controller also accepts return_code as an alias for reason.
If you send a non-BACEN code, the refund will be rejected by BACEN later (async 422 via pix.payout.failed webhook with reason_code filled).
Example
BODY='{"amount":3000,"description":"Devolução acordo","original_transaction_id":"7popu57v6us7p6pcicgq12345","reason":"MD06"}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')
curl -X POST https://api.owem.com.br/api/external/pix/refund \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "hmac: $HMAC" \
-d "$BODY"Success Response -- 202 (async)
Most common path — refund accepted and enqueued.
{
"worked": true,
"transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
"end_to_end_id": "D24313102202604071509K14UmbMt6ck",
"amount": 300000,
"status": "accepted",
"detail": "Devolucao processada"
}HTTP 202 — track the result via GET /transactions/:id (use the transaction_id returned) or wait for the pix.refund.completed / pix.payout.returned webhook.
E2E ID with prefix D
Unlike a regular PIX OUT transaction (E2E prefix E), a refund has E2E with prefix D (for "Devolução"). This prefix identifies the ISO 20022 pacs.004 type at BACEN. The internal transaction_id also has the PIXRET prefix to make identification easier.
Success Response -- 200 (fast-track)
Rare path — refund synchronously settled before the HTTP response returned.
{
"worked": true,
"transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
"end_to_end_id": "D24313102202604071509K14UmbMt6ck",
"amount": 300000,
"status": "settled",
"detail": "Devolução liquidada"
}| Field | Type | Description |
|---|---|---|
worked | Boolean | true indicates the request was accepted |
transaction_id | String | Refund identifier (prefix PIXRET). Use to query status. |
end_to_end_id | String | Refund End-to-End ID at SPI/BACEN (prefix D, not E) |
amount | Integer | Refund amount in subcentavos (÷ 10,000 for BRL). 300000 = R$ 30.00 |
status | String | accepted (HTTP 202 — in processing) or settled (HTTP 200 — settled). Never processing in this POST field. |
detail | String | Descriptive message |
Error Response (404)
{
"worked": false,
"errors": {
"not_found": "Transação original não encontrada"
}
}Error Response (422) -- Insufficient balance
{
"worked": false,
"errors": {
"unprocessable_entity": "insufficient balance"
}
}Happens when the safe balance (min(TB, PG) — see Balance) is less than the requested amount. Refund requires the source account to have balance ≥ the amount to refund.
Error Response (422) -- Amount exceeded
{
"worked": false,
"errors": {
"unprocessable_entity": "Valor da devolução excede o valor original da transação"
}
}Happens when the total of refunds (current + previous) would exceed the original transaction's amount. Partial refunds are allowed, but the sum cannot exceed the received amount.
Error Response (400) -- missing original_transaction_id
{
"worked": false,
"errors": {
"bad_request": "original_transaction_id is required"
}
}Error Response (401)
{
"worked": false,
"errors": {
"unauthorized": "Missing API key credentials. Use Authorization: ApiKey <client_id>:<client_secret>"
}
}Refund deadline (BACEN)
Deadlines defined by BACEN (not enforced locally by the Owem backend — sent straight to SPI and validated there):
MD06(agreement): up to 90 calendar days after receipt of the original transactionBE08(fraud): following BACEN's regulatory MED deadlines (dispute flow, see Infractions)FR01(reported fraud): same BACEN MED deadline- Other codes (
AM09,SL02,RR04): case-by-case validation at BACEN; for operational logic, treat as a maximum deadline of 30 days
The backend does not block refunds outside the deadline — they pass at POST (HTTP 202 accepted) and are rejected later by BACEN via pix.payout.failed webhook with the specific reason_code. Your system should validate the age of the original transaction before calling the API if you need to fail-fast.
PACS.004 vs MED: refunds initiated by BACEN infraction
If the refund you are starting is a consequence of a BACEN infraction (notification via pix.refund.requested), do not call this endpoint. The MED refund is executed automatically by the backend when analysis_result=AGREED is decided (defense not submitted in time OR manual acknowledgment via merchant portal). Calling POST /pix/refund in parallel may generate duplication.
This endpoint is for voluntary refunds initiated by you (e.g., returning an over-paid PIX, cancelling a sale, commercial adjustment). See Infractions (full flow) to distinguish the two scenarios.