Estornar Cobrança
curl --request POST \
--url https://garu.com.br/api/v1/charges/{uuid}/refund \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"amount": 123,
"reason": "<string>"
}
'import requests
url = "https://garu.com.br/api/v1/charges/{uuid}/refund"
payload = {
"amount": 123,
"reason": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({amount: 123, reason: '<string>'})
};
fetch('https://garu.com.br/api/v1/charges/{uuid}/refund', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://garu.com.br/api/v1/charges/{uuid}/refund",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'amount' => 123,
'reason' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://garu.com.br/api/v1/charges/{uuid}/refund"
payload := strings.NewReader("{\n \"amount\": 123,\n \"reason\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://garu.com.br/api/v1/charges/{uuid}/refund")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"amount\": 123,\n \"reason\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://garu.com.br/api/v1/charges/{uuid}/refund")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"amount\": 123,\n \"reason\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyCobranças
Estornar Cobrança
Estorne uma cobrança paga, total ou parcialmente
POST
/
api
/
v1
/
charges
/
{uuid}
/
refund
Estornar Cobrança
curl --request POST \
--url https://garu.com.br/api/v1/charges/{uuid}/refund \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"amount": 123,
"reason": "<string>"
}
'import requests
url = "https://garu.com.br/api/v1/charges/{uuid}/refund"
payload = {
"amount": 123,
"reason": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({amount: 123, reason: '<string>'})
};
fetch('https://garu.com.br/api/v1/charges/{uuid}/refund', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://garu.com.br/api/v1/charges/{uuid}/refund",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'amount' => 123,
'reason' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://garu.com.br/api/v1/charges/{uuid}/refund"
payload := strings.NewReader("{\n \"amount\": 123,\n \"reason\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://garu.com.br/api/v1/charges/{uuid}/refund")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"amount\": 123,\n \"reason\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://garu.com.br/api/v1/charges/{uuid}/refund")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"amount\": 123,\n \"reason\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyVisão Geral
Estorna uma cobrança já paga. O estorno pode ser total (omitindoamount) ou parcial.
Só cobranças com status
paid podem ser estornadas. Para cancelar uma cobrança que ainda não foi paga (pending), use DELETE /api/v1/charges/{uuid}.Headers
string
required
Sua chave de API (
Bearer sk_live_...)string
required
application/jsonstring
Opcional. Só se aplica a Pix/boleto, que abrem um pedido de estorno em vez de reverter automaticamente — a mesma chave devolve o pedido original por 24h em vez de abrir um segundo. Ignorado para cartão, que reverte na hora e não tem pedido para duplicar.
Path Parameters
string
required
UUID da cobrança
Request Body
number
Valor a estornar em BRL decimal (ex:
50.00, não 5000). Omita para estornar o total cobrado.string
Motivo do estorno, até 500 caracteres. Fica registrado na cobrança.
O teto do estorno é o
chargedTotal da cobrança, não o amount. Numa venda parcelada no cartão o cliente pagou o valor com acréscimo, e é esse valor que pode voltar: um produto de R349,00vendidoem2×porR 358,52 aceita estorno de até R$ 358,52.Exemplo de Requisição
curl -X POST https://garu.com.br/api/v1/charges/6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f/refund \
-H "Authorization: Bearer sk_live_sua_chave_api" \
-H "Content-Type: application/json" \
-d '{ "amount": 100.00, "reason": "Cliente desistiu" }'
const response = await fetch(
`https://garu.com.br/api/v1/charges/${chargeUuid}/refund`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GARU_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: 100.0, reason: 'Cliente desistiu' })
}
);
const charge = await response.json();
console.log(charge.refund);
import os
import requests
response = requests.post(
f"https://garu.com.br/api/v1/charges/{charge_uuid}/refund",
headers={
"Authorization": f"Bearer {os.environ['GARU_API_KEY']}",
"Content-Type": "application/json",
},
json={"amount": 100.00, "reason": "Cliente desistiu"},
)
print(response.json()["refund"])
Resposta de Sucesso (200 OK)
A cobrança atualizada, agora com o blocorefund preenchido:
{
"uuid": "6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f",
"status": "reversed",
"paymentMethod": "pix",
"amount": 349.00,
"chargedTotal": 349.00,
"refund": {
"amount": 100.00,
"reason": "Cliente desistiu",
"refundedAt": "2026-07-23T10:00:00.000Z"
}
}
Estorno de Pix Automático
Cobranças de Pix Automático são estornadas via devolução — uma transferência Pix de volta, regida por regras do Banco Central. Isso muda o comportamento em um ponto importante:A devolução não é instantânea. A resposta confirma que o estorno foi solicitado, não concluído.A cobrança fica com
status: "refundPending" e o bloco refund já traz o valor, mas com refundedAt: null. Quando a transferência liquida, o status vira reversed, o refundedAt é preenchido e o webhook transaction.refunded é disparado.Use refundedAt como o sinal de que o dinheiro voltou de fato — não a resposta desta chamada.Estorno de PIX (Celcoin) não é suportado
A Celcoin não oferece devolução de PIX: a API dela expõe estorno apenas para cartão e boleto, e o painel não mostra ação de estorno para um PIX pago. Um estorno de PIX processado pela Celcoin não é possível por aqui — a API recusa na hora, sem sequer tentar o provedor.A API sinaliza esse caso com HTTP 422 e o campo
code: "pix_refund_unsupported". É uma recusa definitiva — não adianta tentar de novo. Para devolver um PIX Celcoin, faça a transferência de volta manualmente ao cliente (por exemplo, um Pix de volta).422 Unprocessable Entity
{
"statusCode": 422,
"code": "pix_refund_unsupported",
"message": "Estorno de Pix não é suportado para cobranças processadas pela Celcoin. Faça a devolução manualmente ao cliente (por exemplo, um Pix de volta).",
"path": "/api/v1/charges/{uuid}/refund"
}
Auditoria
Todo estorno registra quem o fez. Um estorno feito pelo painel guarda o usuário responsável; um estorno feito por esta API guarda qual chave de API foi usada. Isso vale para a trilha de auditoria exigida pelo PCI DSS — mais um motivo para usar chaves distintas por integração e revogar as que não estiverem em uso.Erros
| Código | Quando acontece |
|---|---|
400 | Cobrança não está em status estornável, já foi estornada, tem um estorno em processamento, ou o valor excede o chargedTotal |
401 | Chave de API ausente ou inválida |
404 | A cobrança não existe ou não pertence à conta da chave usada |
422 | Estorno de PIX processado pela Celcoin — não suportado. Recusa definitiva; o corpo traz code: "pix_refund_unsupported" (veja acima) |
Was this page helpful?