Pay-In Refunds
Refund a Pix charge you received (BRL back to the payer + USDT recovery)
This endpoint refunds a Pay-In (a Pix charge that was paid to you). A refund has two legs:
- BRL leg — the original BRL amount (full or partial) is returned to the payer's bank account through the gateway.
- USDT leg — the USDT kamiPay already settled to your wallet for that charge is recovered from your per‑checkout refund wallet back into kamiPay.
Both legs are tracked independently and reported together in the status endpoint and the refund webhook.
This is for Pay-In refunds — refunding a charge you received. It is unrelated to refunds of Pay-Outs you sent (those appear under Lots with rpt_… ids). Pay-In refunds use rpir_… / rpi_… ids.
Prerequisite — contact support first. To enable refunds you must reach out to kamiPay support. They will assign you a dedicated refund wallet (per checkout) and give you its address. You then have to pre‑fund that wallet with USDT (on Polygon): every refund recovers the USDT from this wallet, so it must hold enough balance (plus a little POL for gas) before you can refund. Without a funded refund wallet, refund requests are rejected.
Request Body
You must provide exactly one identifier for the original charge:
| Name | Type | Description |
|---|---|---|
| operation_id | string | The PIX operation id (ptxr_…) returned as operation_id when you created the charge. One identifier required. |
| pay_in_transaction_request_id | string | Alias of operation_id (same ptxr_… value). One identifier required. |
| end_to_end_id | string | The end‑to‑end id of the original cash‑in (BACEN E…). One identifier required. |
| value | float | Optional. Partial refund amount in BRL, with at most 2 decimal places. Must be > 0 and ≤ the original amount. If omitted, the full original amount is refunded. |
| refund_reason | string | Optional. One of customer_request, duplicated, fraud, internal_error. |
| detail | string | Optional. Free‑text note (max 2000 chars). |
Partial refunds are supported. Multiple partial refunds on the same charge are allowed as long as their sum does not exceed the original amount (pending and succeeded refunds both count against that limit).
Example Request
const url = `${baseURL}/v2/pay_in_refunds/request`;
const body = {
"operation_id": "ptxr_01kr3m9p7n2s4d8h6e5b3t7w1k",
// "value": 5.05, // optional: partial refund in BRL; omit for full
"refund_reason": "customer_request",
"detail": "duplicated charge"
};
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body)
});import requests
url = f"{base_url}/v2/pay_in_refunds/request"
body = {
"operation_id": "ptxr_01kr3m9p7n2s4d8h6e5b3t7w1k",
# "value": 5.05, # optional: partial refund in BRL; omit for full
"refund_reason": "customer_request",
"detail": "duplicated charge",
}
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
}
response = requests.post(url, json=body, headers=headers)Response
A successful request returns 202 Accepted: the refund has been validated and persisted, the BRL refund is being sent to the gateway, and the USDT recovery will be triggered automatically once the BRL refund is confirmed.
{
"pay_in_refund_request_id": "rpir_9e7f2fc2c0b14a8d9a1e3f5b7c2d4e6a",
"end_to_end_id": "E10573521202606051851hovcb9HXqev",
"requested_value_brl": "5.05",
"requested_value_usdt": "1.800000",
"refund_wallet_id": 12,
"refund_reason": "customer_request",
"detail": "duplicated charge"
}Track the refund afterwards with pay_in_refund_request_id (rpir_…) via the status endpoint, or wait for the refund webhook.
{ "detail": "value out of range" }{ "detail": "no refundable charge found for the provided identifier" }// Refunds are not enabled for this checkout (no refund wallet assigned).
// Contact support to enable refunds before using this endpoint.
{ "detail": "refunds not enabled for this checkout" }// The assigned refund wallet is missing or disabled.
{ "detail": "refund wallet missing or disabled" }// The refund wallet does not hold enough balance to cover the recovery.
{ "detail": "insufficient USDT balance in refund wallet: available=... required=..." }{ "detail": "Incorrect Credentials" }Validation & errors
The request is rejected (nothing is refunded) when:
| Status | Reason |
|---|---|
400 | value is ≤ 0, has more than 2 decimals, or exceeds the original amount. |
404 | No refundable charge matches the identifier or the charge belongs to another merchant (both return the same "not found" so a merchant can't probe other merchants' transactions). |
409 | The charge is not merchant‑linked, is itself a refund, refunds are not enabled for the checkout, the requested amount would exceed the original (given prior refunds), the original USDT settlement hasn't mined yet, or the refund wallet has insufficient balance (the message states whether USDT or POL/gas is short). |
503 | The refund wallet balance could not be read on‑chain (transient) — retry later. |
The USDT leg is recovered from your refund wallet, so it must hold enough USDT (to cover the refund, net of any in‑flight recoveries) and enough POL to pay gas. If either is short the request is rejected up‑front with a 409 naming the currency.