Skip to content

PIX Refund

Initiates a refund (total or partial) of a received PIX transaction.

Endpoint

POST /api/external/pix/refund

Headers

HeaderTypeRequiredDescription
AuthorizationStringYesApiKey {client_id}:{client_secret}
Content-TypeStringYesapplication/json
hmacStringYesHMAC-SHA512 signature of the body (learn more)
Idempotency-KeyStringNoUnique key to prevent duplicate processing (max 256 chars)
X-Key-CaseStringNoSet to camelCase to receive response fields in camelCase (default is snake_case)

Request Body

FieldTypeRequiredDescriptionExample
original_transaction_idStringYesID of the original received PIX transaction (see aliases below)"7popu57v6us7p6pcicgq12345"
amountIntegerNoAmount to refund in centavos in the request. If omitted, refunds the original transaction's full amount.3000 (R$ 30.00)
reasonStringNoBACEN refund code (see table below). Default: MD06. Not validated locally — sent straight to BACEN."MD06"
descriptionStringNoRefund 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 (prefix E)
    • originalTransactionId — camelCase
    • transaction_id — fallback
    • end_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 original end_to_end_id
  • remaining_refundable = original_amount - total_refunded (how much can still be refunded)
  • is_partial = true if 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

CodeDescription
MD06Refund by agreement between parties
BE08Fraud
AM09Incorrect amount
SL02Settlement error
RR04Unrecognized transaction (BACEN)
FR01Fraud 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.received webhook: the backend maintains a local mapping (return_reasons) for MD06, 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

bash
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.

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

json
{
  "worked": true,
  "transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
  "end_to_end_id": "D24313102202604071509K14UmbMt6ck",
  "amount": 300000,
  "status": "settled",
  "detail": "Devolução liquidada"
}
FieldTypeDescription
workedBooleantrue indicates the request was accepted
transaction_idStringRefund identifier (prefix PIXRET). Use to query status.
end_to_end_idStringRefund End-to-End ID at SPI/BACEN (prefix D, not E)
amountIntegerRefund amount in subcentavos (÷ 10,000 for BRL). 300000 = R$ 30.00
statusStringaccepted (HTTP 202 — in processing) or settled (HTTP 200 — settled). Never processing in this POST field.
detailStringDescriptive message

Error Response (404)

json
{
  "worked": false,
  "errors": {
    "not_found": "Transação original não encontrada"
  }
}

Error Response (422) -- Insufficient balance

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

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

json
{
  "worked": false,
  "errors": {
    "bad_request": "original_transaction_id is required"
  }
}

Error Response (401)

json
{
  "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 transaction
  • BE08 (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.

Owem Pay Instituição de Pagamento — ISPB 37839059