Skip to main content

Refund Guide (PIX Refund)

This guide explains how to request a refund for a received PIX payment.

Overview

The PIX refund allows you to reverse a received payment in full or in part. The original transaction is always identified by its E2E (End-to-End ID) — there is no refund by charge identifier. If you only have the identifier, look up the QR code or the payment first to get the E2E (see Where to Find the E2E).

When to Use

  • Duplicate payment - Customer paid twice
  • Order cancellation - Order cancelled after payment
  • Incorrect amount - Customer paid a different amount than expected
  • Operational error - Payment received incorrectly

Deadlines

The PIX refund deadline is set by BACEN and enforced by the settlement bank: 90 days from the original payment, for both ordinary refunds and fraud cases.

warning

Past that deadline the settlement bank refuses the refund — the API forwards the refusal as partner_rejected (422), with the stated reason in the partner block. There is no dedicated "deadline expired" error code. In that scenario, use another reversal method.

Request Refund by E2E (End-to-End ID)

The E2E is the unique transaction identifier in the Brazilian PIX system. Format: E{ISPB}{DATE}{SEQUENTIAL}.

Request

curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/out/refund" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-e2e-12345" \
-d '{
"originalEndToEnd": "E36741675202601281435001234567",
"amount": 150.00,
"reason": "user-requested",
"identifier": "refund-order-12345"
}'

Body Parameters

The account that received the original PIX comes from the path ({accountId}) and the currency is always BRL — neither is read from the body.

FieldTypeRequiredDescription
originalEndToEndstringYesE2E ID of the original transaction to be refunded
amountnumberYesAmount to refund in BRL — equal to the original PIX amount (full refund) or smaller (partial refund). It cannot exceed it
reasonstringYesRefund reason kebab-case slug (see table below)
identifierstringNoRefund identifier (max 38 chars [A-Za-z0-9._-]). Auto-generated when omitted
descriptionstringNoFree-form description (max 140 characters)

reason Values

The list is closed: any value outside this table is rejected with HTTP 400. Slugs are forwarded verbatim to the partner bank (MT Bank), with no translation.

SlugWhen to use
user-requestedEnd customer requested the refund (most common case)
transaction-errorGeneric transaction error (wrong amount, inconsistent data)
unauthorized-transactionTransaction not authorized by the holder
fraudConfirmed/suspected fraud
trade-disagreementCommercial dispute (good/service not delivered)
withdrawal-purchaseOperation involving PIX Saque/Troco
contractual-divergenceContractual disagreement between parties
operational-errorOperational/banking processing error
duplicate-paymentDuplicate payment

Success Response

Refund runs through the same pipeline (and shape) as PIX out. The HTTP status reflects the outcome: 200 (COMPLETED), 422 (FAILED), 202 (TIMEOUT/PENDING).

{
"paymentId": "pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"transactionId": "txn-refund-abc123",
"endToEndId": "D12345678202301011500009876543",
"status": "COMPLETED",
"completedAt": "2026-01-28T15:00:03Z",
"identifier": "refund-order-12345"
}
FieldDescription
paymentIdInternal payment intent ID (tracking/reconciliation)
endToEndIdRefund D-code/E2E (once registered)
statusCOMPLETED, FAILED, TIMEOUT, PENDING, PROCESSING
errorCode / errorReasonset when FAILED

There is no refundId/amount/currency field — use paymentId and track the final outcome via the statement or webhook.

Partial refunds

The amount field is required and defines how much goes back to the payer: send the original PIX amount to refund it in full, or a smaller value to refund part of it. Above the original, the API returns 400 refund_amount_exceeded and does not start the refund.

The same PIX can be refunded in more than one installment, up to the original amount. Two rules matter here:

  • Each refund needs its own Idempotency-Key and identifier. Reusing either makes the API treat the call as a retry of the previous refund and return that result — the second installment never goes out.
  • The banking partner owns the refundable balance. CorpX does not add up partial refunds: a refund that exceeds what is still refundable is rejected by the partner and comes back as refund_amount_exceeded.

To find out how much of a PIX has already been refunded, check the account statement: refund entries reference the original transaction's E2E.

Refund Webhook

When the refund is processed, you receive a webhook:

{
"id": "pix-refund-pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"type": "pix.refund.completed",
"occurredAt": "2026-01-28T15:00:03.000000000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-yourcompany",
"accountId": "{accountId}",
"data": {
"paymentId": "pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"tenantId": "tenant-yourcompany",
"accountId": "{accountId}",
"status": "SUCCESS",
"endToEnd": "D36741675202601281500009876543",
"transactionId": "txn-refund-abc123",
"amount": 150.00,
"currency": "BRL",
"identifier": "refund-order-12345",
"description": "",
"originalTransactionId": "E36741675202601281435001234567",
"initiatedAt": "2026-01-28T15:00:01.000000000Z",
"completedAt": "2026-01-28T15:00:03.000000000Z",
"payee": {
"name": "John Smith",
"document": "12345678901"
}
}
}

A refused refund emits pix.refund.failed with status: "FAILED" and the errorCode/errorReason/error fields inside data. A refund in TIMEOUT emits no webhook — check the statement.

Full Example: Refund Script

#!/bin/bash

# Configuration
API_URL="https://tenant.api.corpx.com"
TOKEN="your_token_here"
TENANT_ID="tenant-suaempresa"
ACCOUNT_ID="your_account"

# Refund by E2E function
refund_by_e2e() {
local e2e=$1
local amount=$2
local reason=$3

echo "Requesting refund..."
echo "E2E: $e2e"
echo "Amount: R$ $amount"
echo "Reason: $reason"
echo ""

IDEMPOTENCY_KEY="refund-$(date +%s)-$RANDOM"

response=$(curl -s -X POST "$API_URL/v1/accounts/$ACCOUNT_ID/pix/out/refund" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: $TENANT_ID" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d "{
\"accountId\": \"$ACCOUNT_ID\",
\"originalEndToEnd\": \"$e2e\",
\"amount\": $amount,
\"currency\": \"BRL\",
\"reason\": \"$reason\"
}")

echo "$response" | jq
}

# Usage
echo "=== PIX REFUND ==="
echo ""
read -p "E2E (End-to-End ID): " e2e
read -p "Amount to refund: " amount
read -p "Reason: " reason

refund_by_e2e "$e2e" "$amount" "$reason"

Where to Find the E2E

The E2E can be found in:

  1. Payment received webhook response
  2. Charge lookup after payment
  3. Account statement (Statement)
  4. Payer's receipt

Example: Extract E2E from Webhook

{
"id": "evt_in_123",
"type": "pix.in.completed",
"occurredAt": "2026-01-28T14:35:00.000Z",
"schemaVersion": "1.0",
"tenantId": "tenant-yourcompany",
"accountId": "{accountId}",
"data": {
"identifier": "cob_abc123def456",
"endToEnd": "E36741675202601281435001234567",
"amount": 150.00
}
}

The field to store is data.endToEnd.

Example: Extract E2E from Charge (QR Code)

curl -X GET "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/qr-code/lookup?identifier=order-12345" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa"
{
"identifier": "order-12345",
"status": "PAID",
"endToEndId": "E36741675202601281435001234567"
}

The QR lookup response is a flat object (no data envelope); the field to store is endToEndId.

Common Errors

ErrorHTTPCauseSolution
missing_fields400originalEndToEnd or amount missingSend both fields
invalid_payload400reason outside the closed list, or malformed JSONUse one of the slugs from the table above
refund_amount_exceeded400amount greater than the original PIX, or greater than the balance still refundableCheck the statement for how much was already refunded
original_transaction_not_found404E2E not found at the settlement bankCheck originalEndToEnd
conflict409Transaction has already been fully refundedCheck the history in the statement
insufficient_funds422Insufficient balance for the refundDeposit funds into the account
policy_denied422A tenant/account policy rule refused the refundCheck violations in the body — Policies and Rules
partner_rejected422Refused by the settlement bank (includes an expired 90-day deadline)Check the partner block for the stated reason
partner_error502Failed to look up the original transaction at the settlement bankRetry; if it persists, contact support

The full list is in Errors.

Best Practices

  1. Save the E2E of all received transactions
  2. Use Idempotency Key to avoid duplicate refunds — and a different key (and identifier) for each partial refund of the same PIX
  3. Check the statement for how much of a PIX has already been refunded before requesting another partial refund
  4. Document the reason for auditing purposes
  5. Configure webhooks to track the status
  6. Keep a history of refunds for reconciliation

Next Steps