curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"amount": 1000,
"note": "Customer returned one item.",
"idempotency_key": "refund-2026-08-25-7f3c9a"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund"
payload = {
"amount": 1000,
"note": "Customer returned one item.",
"idempotency_key": "refund-2026-08-25-7f3c9a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
amount: 1000,
note: 'Customer returned one item.',
idempotency_key: 'refund-2026-08-25-7f3c9a'
})
};
fetch('https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/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://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/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' => 1000,
'note' => 'Customer returned one item.',
'idempotency_key' => 'refund-2026-08-25-7f3c9a'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund"
payload := strings.NewReader("{\n \"amount\": 1000,\n \"note\": \"Customer returned one item.\",\n \"idempotency_key\": \"refund-2026-08-25-7f3c9a\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"amount\": 1000,\n \"note\": \"Customer returned one item.\",\n \"idempotency_key\": \"refund-2026-08-25-7f3c9a\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"amount\": 1000,\n \"note\": \"Customer returned one item.\",\n \"idempotency_key\": \"refund-2026-08-25-7f3c9a\"\n}"
response = http.request(request)
puts response.read_body{
"transaction_id": "ch_T7mK2p9Q4r6S8v",
"status": "partially_refunded",
"payment_type": "one_time",
"created_date": "2026-08-25T10:00:00Z",
"currency": "USD",
"amount": 2500,
"fee": 103,
"net": 2397,
"service_fee": 50,
"refunded_amount": 1000,
"refunds": [
{
"amount": 1000,
"created_date": "2026-08-26T09:15:00Z",
"id": "re_T7mK2p9Q4r6S8v",
"status": "succeeded"
}
],
"method_kind": "card",
"method_moto": false,
"card_network": "visa",
"card_masked": "4242",
"card_holder": "Jane Doe",
"provider_id": "stripe",
"merchant_account_id": "4f1c2a7e-9b3d-4e8f-a6c5-2d7e9f0b1a3c",
"display_order_id": "10042",
"items": [
{
"amount": 2500,
"name": "Oak side table",
"quantity": 1
}
],
"billing": {
"address": "500 Terry Francine St",
"city": "San Francisco",
"country_code": "US",
"email": "jane@example.com",
"name": "Jane Doe",
"phone": "+14155550123",
"postal_code": "94158",
"state": "CA"
},
"shipping": {
"address": "500 Terry Francine St",
"city": "San Francisco",
"country_code": "US",
"name": "Jane Doe",
"phone": "+14155550123",
"postal_code": "94158",
"state": "CA"
},
"disputes": [],
"history": [
{
"date": "2026-08-26T09:15:00Z",
"kind": "refund",
"lines": [
{
"amount": 1000,
"label": "refunded_amount"
}
],
"status": "succeeded"
},
{
"date": "2026-08-25T10:00:00Z",
"kind": "payment",
"lines": [
{
"amount": 2500,
"label": "amount"
},
{
"amount": -103,
"label": "processing_fee"
},
{
"amount": 2397,
"label": "net"
}
],
"status": "succeeded"
}
],
"decline_reason": "Your card has insufficient funds."
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Refund payment transaction
Refunds all or part of a payment through its payment provider, then returns the payment’s details after the refund.
Send amount in the smallest unit of the payment’s currency. Refunding less than the full amount is a partial refund, and you can refund again later up to what’s left. The provider checks the amount, so one above the refundable balance is rejected.
Send an idempotency key that stays the same across retries of one refund attempt, and a new one for each new refund. Put it in the Idempotency-Key header or in idempotency_key in the body. A retry with the same key after the refund went through returns the payment without refunding again, and a retry with the same key but a different amount is rejected. For Stripe, the key is also passed to Stripe as its own idempotency key.
Wix has no idempotency key of its own. When a Wix refund ends without a clear answer, its key stays reserved and a retry with it is rejected, so check refunds with Get payment transaction before trying again with a new key.
Each refund is recorded in the workspace’s audit log as a payments.transaction.refunded event.
This is limited to 30 requests an hour per user. Some workspaces have a different limit.
curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"amount": 1000,
"note": "Customer returned one item.",
"idempotency_key": "refund-2026-08-25-7f3c9a"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund"
payload = {
"amount": 1000,
"note": "Customer returned one item.",
"idempotency_key": "refund-2026-08-25-7f3c9a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
amount: 1000,
note: 'Customer returned one item.',
idempotency_key: 'refund-2026-08-25-7f3c9a'
})
};
fetch('https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/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://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/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' => 1000,
'note' => 'Customer returned one item.',
'idempotency_key' => 'refund-2026-08-25-7f3c9a'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund"
payload := strings.NewReader("{\n \"amount\": 1000,\n \"note\": \"Customer returned one item.\",\n \"idempotency_key\": \"refund-2026-08-25-7f3c9a\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"amount\": 1000,\n \"note\": \"Customer returned one item.\",\n \"idempotency_key\": \"refund-2026-08-25-7f3c9a\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/payments/analytics/transactions/{transaction_id}/refund")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"amount\": 1000,\n \"note\": \"Customer returned one item.\",\n \"idempotency_key\": \"refund-2026-08-25-7f3c9a\"\n}"
response = http.request(request)
puts response.read_body{
"transaction_id": "ch_T7mK2p9Q4r6S8v",
"status": "partially_refunded",
"payment_type": "one_time",
"created_date": "2026-08-25T10:00:00Z",
"currency": "USD",
"amount": 2500,
"fee": 103,
"net": 2397,
"service_fee": 50,
"refunded_amount": 1000,
"refunds": [
{
"amount": 1000,
"created_date": "2026-08-26T09:15:00Z",
"id": "re_T7mK2p9Q4r6S8v",
"status": "succeeded"
}
],
"method_kind": "card",
"method_moto": false,
"card_network": "visa",
"card_masked": "4242",
"card_holder": "Jane Doe",
"provider_id": "stripe",
"merchant_account_id": "4f1c2a7e-9b3d-4e8f-a6c5-2d7e9f0b1a3c",
"display_order_id": "10042",
"items": [
{
"amount": 2500,
"name": "Oak side table",
"quantity": 1
}
],
"billing": {
"address": "500 Terry Francine St",
"city": "San Francisco",
"country_code": "US",
"email": "jane@example.com",
"name": "Jane Doe",
"phone": "+14155550123",
"postal_code": "94158",
"state": "CA"
},
"shipping": {
"address": "500 Terry Francine St",
"city": "San Francisco",
"country_code": "US",
"name": "Jane Doe",
"phone": "+14155550123",
"postal_code": "94158",
"state": "CA"
},
"disputes": [],
"history": [
{
"date": "2026-08-26T09:15:00Z",
"kind": "refund",
"lines": [
{
"amount": 1000,
"label": "refunded_amount"
}
],
"status": "succeeded"
},
{
"date": "2026-08-25T10:00:00Z",
"kind": "payment",
"lines": [
{
"amount": 2500,
"label": "amount"
},
{
"amount": -103,
"label": "processing_fee"
},
{
"amount": 2397,
"label": "net"
}
],
"status": "succeeded"
}
],
"decline_reason": "Your card has insufficient funds."
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Authorizations
Personal access token, sent as Authorization: Bearer <token>.
Headers
Key for this refund attempt, the same as idempotency_key in the body. Send it in either place, or in both with the same value.
1 - 100Path Parameters
ID of the Base44 app.
ID of the transaction, taken from transaction_id on a row from List payment transactions.
Query Parameters
Payment provider whose transactions to use. Either stripe or wix. stripe covers payments taken through the app's Stripe integration, and wix covers payments taken through Wix Payments (Base44 Payments). The provider has to be connected to the app.
stripe, wix The row's own timestamp from List payment transactions, which makes the lookup faster. Leave it out rather than guess, since a wrong value can make the transaction look like it isn't the app's.
Body
A refund of all or part of one payment.
Amount to refund, in the smallest unit of the payment's own currency. Must be above 0.
1000
Private note stored with the refund, up to 500 characters. Defaults to null.
500"Customer returned one item."
Key you generate for this refund attempt, up to 100 characters. Keep it the same across retries of one attempt, and use a new one for each new refund. Required unless you send it in the Idempotency-Key header instead.
1 - 100"refund-2026-08-25-7f3c9a"
Response
The provider accepted the refund. The body is the payment after it, or the payment from before it when the details can't be read again right away.
One payment in full, as its payment provider reports it.
Provider ID of the payment. For Stripe this is the charge, which can differ from the ID you looked it up by.
"ch_T7mK2p9Q4r6S8v"
Where the payment stands. Either succeeded, pending, failed, refunded, partially_refunded, pending_refund, or chargeback. Other provider values come through in lowercase.
"partially_refunded"
Either one_time or recurring, or an empty string when the provider doesn't say.
"one_time"
When the payment was made, as an ISO 8601 timestamp. Empty when the provider doesn't say.
"2026-08-25T10:00:00Z"
Three-letter ISO 4217 code of the payment's currency, in uppercase. Defaults to USD.
"USD"
Amount paid, in the smallest unit of the payment's currency.
2500
Processing fee the provider took, in the smallest unit of the payment's currency, or null when none was taken, as on a declined payment.
103
What the merchant kept after the fee, in the smallest unit of the payment's currency, or null when no money moved.
2397
Application fee taken on top of the processing fee, in the smallest unit of the payment's currency, or null when there is none.
50
Total refunded so far, in the smallest unit of the payment's currency.
1000
Refunds against the payment. Empty when there are none.
Show child attributes
Show child attributes
[
{
"amount": 1000,
"created_date": "2026-08-26T09:15:00Z",
"id": "re_T7mK2p9Q4r6S8v",
"status": "succeeded"
}
]
Payment method type as the provider names it, for example card. Empty when the provider doesn't say.
"card"
true when the merchant entered the card for the customer, as in a phone or mail order, and false otherwise. Always false for Stripe.
false
Card network, or null when the payment wasn't by card.
"visa"
Masked card number, or null when the payment wasn't by card. Stripe gives only the last four digits.
"4242"
Cardholder name, or null when the provider doesn't have it.
"Jane Doe"
Processor that handled the payment. Always stripe for Stripe. For Wix, the processor Wix routed the payment through.
"stripe"
Merchant account at the processor, or null when unknown. Always null for Stripe.
"4f1c2a7e-9b3d-4e8f-a6c5-2d7e9f0b1a3c"
Order number shown to the customer, or null when there is none. Always null for Stripe.
"10042"
Items bought. Empty when the provider doesn't list them.
Show child attributes
Show child attributes
[
{
"amount": 2500,
"name": "Oak side table",
"quantity": 1
}
]
Billing details the customer gave, or null when there are none.
Show child attributes
Show child attributes
{
"address": "500 Terry Francine St",
"city": "San Francisco",
"country_code": "US",
"email": "jane@example.com",
"name": "Jane Doe",
"phone": "+14155550123",
"postal_code": "94158",
"state": "CA"
}
Shipping details, or null when the payment has none.
Show child attributes
Show child attributes
{
"address": "500 Terry Francine St",
"city": "San Francisco",
"country_code": "US",
"name": "Jane Doe",
"phone": "+14155550123",
"postal_code": "94158",
"state": "CA"
}
Disputes against the payment. Empty when there are none.
Show child attributes
Show child attributes
[]
Timeline of the payment, its refunds, and its disputes, newest first.
Show child attributes
Show child attributes
[
{
"date": "2026-08-26T09:15:00Z",
"kind": "refund",
"lines": [
{
"amount": 1000,
"label": "refunded_amount"
}
],
"status": "succeeded"
},
{
"date": "2026-08-25T10:00:00Z",
"kind": "payment",
"lines": [
{ "amount": 2500, "label": "amount" },
{ "amount": -103, "label": "processing_fee" },
{ "amount": 2397, "label": "net" }
],
"status": "succeeded"
}
]
Why the payment was declined, as the provider words it, or null when it wasn't declined.
"Your card has insufficient funds."
Was this page helpful?