curl -X POST https://api.liddie.io/api/v1/payments/6a0000000000000000000001/refund \
-H "Authorization: Bearer lid_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-20260713-0001" \
-d '{
"amount": "10",
"destinationAddress": "T..."
}'
const response = await fetch(
'https://api.liddie.io/api/v1/payments/6a0000000000000000000001/refund',
{
method: 'POST',
headers: {
'Authorization': 'Bearer lid_live_...',
'Content-Type': 'application/json',
'Idempotency-Key': 'refund-20260713-0001',
},
body: JSON.stringify({
amount: '10',
destinationAddress: 'T...',
}),
},
);
const { ok, data } = await response.json();
{
"ok": true,
"data": {
"id": "<refundId>",
"paymentId": "...",
"currency": "USDT_TRC20",
"amount": "10",
"netToDestination": "9.2",
"networkFee": "0.8",
"destination": "T...",
"status": "pending"
}
}
Refunds
Create a refund
Refund a settled Liddie payment by sending up to its credited net back to a payer-supplied address — the platform fee is never refunded.
POST
/
api
/
v1
/
payments
/
{paymentId}
/
refund
curl -X POST https://api.liddie.io/api/v1/payments/6a0000000000000000000001/refund \
-H "Authorization: Bearer lid_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-20260713-0001" \
-d '{
"amount": "10",
"destinationAddress": "T..."
}'
const response = await fetch(
'https://api.liddie.io/api/v1/payments/6a0000000000000000000001/refund',
{
method: 'POST',
headers: {
'Authorization': 'Bearer lid_live_...',
'Content-Type': 'application/json',
'Idempotency-Key': 'refund-20260713-0001',
},
body: JSON.stringify({
amount: '10',
destinationAddress: 'T...',
}),
},
);
const { ok, data } = await response.json();
{
"ok": true,
"data": {
"id": "<refundId>",
"paymentId": "...",
"currency": "USDT_TRC20",
"amount": "10",
"netToDestination": "9.2",
"networkFee": "0.8",
"destination": "T...",
"status": "pending"
}
}
A refund sends up to the payment’s credited net back to an address the payer gives you. The platform fee is never refunded, and the refund’s own network fee comes out of the amount sent.
The egress path can also pass through
A refund is a new outbound transfer — crypto transfers are irreversible. Always confirm the destination address with the payer before you send.
curl -X POST https://api.liddie.io/api/v1/payments/6a0000000000000000000001/refund \
-H "Authorization: Bearer lid_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-20260713-0001" \
-d '{
"amount": "10",
"destinationAddress": "T..."
}'
const response = await fetch(
'https://api.liddie.io/api/v1/payments/6a0000000000000000000001/refund',
{
method: 'POST',
headers: {
'Authorization': 'Bearer lid_live_...',
'Content-Type': 'application/json',
'Idempotency-Key': 'refund-20260713-0001',
},
body: JSON.stringify({
amount: '10',
destinationAddress: 'T...',
}),
},
);
const { ok, data } = await response.json();
{
"ok": true,
"data": {
"id": "<refundId>",
"paymentId": "...",
"currency": "USDT_TRC20",
"amount": "10",
"netToDestination": "9.2",
"networkFee": "0.8",
"destination": "T...",
"status": "pending"
}
}
Authorization
There are two ways to authorize a refund, depending on where the call comes from:- Dashboard (JWT): requires a merchant role (
merchant_admin,merchant_member, orsuper_admin), therefunds:manageteam permission, an active merchant account, and a fresh strong factor — passkey, TOTP, or an emailed code — in the request body. A member withoutrefunds:manageis refused with403before any MFA is evaluated; a suspended merchant cannot refund at all. Request the emailed code via Send refund email code. - API key: requires the elevated, opt-in
refunds:createscope and an active merchant account. There is no per-call MFA — key possession plus the scope is the authorization (the Stripe model), optionally narrowed by the key’s IP whitelist if you configure one (strongly recommended forrefunds:createkeys: an empty whitelist accepts the key from any IP). Grant this scope only to hardened backends. Every API-key refund also raises a dashboard notification to the merchant.
Parameters
string
required
ID of the payment to refund. The payment must be settled — settlement (funds moving to storage, shown as “Processing” in the dashboard while under way) must have completed.
string
required
Decimal string (preferred) or number. Must be less than or equal to the net credited minus prior refunds.
string
Payer’s address, 4–256 characters (API naming). One of
destinationAddress or address is required.string
Same destination, 4–256 characters (dashboard naming). The API path conventionally sends
destinationAddress; both are accepted.string
Always send this header on the API path. A retry with the same key normally replays the original refund instead of paying twice — but the replay is status-aware: if the original refund failed before broadcast, retrying the same key returns
409 REFUND_FAILED_RETRY_NEW_KEY, and you must retry with a fresh key (do not loop on the same one). Format: 1–255 printable ASCII characters, no spaces — a non-conforming key is rejected with 400 INVALID_IDEMPOTENCY_KEY.MFA fields (dashboard sessions)
MFA fields (dashboard sessions)
These fields apply only on the dashboard (JWT) path. One of them is required as the fresh strong factor.
string
The single-use emailed code (see Send refund email code).
string
TOTP code — an alternative to
emailCode / passkey.object
WebAuthn assertion. Sent together with
challengeKey.string
Accompanies
passkeyResponse.Ignored fields
Ignored fields
string
Echo only — ignored; the refund is always in the payment’s currency.
Server-side guards that always apply
Server-side guards that always apply
On either auth path, every refund is checked against: settled payments only, an atomic per-payment refund cap, address format validation, tenant scoping (you can only refund your own payments), and idempotent egress (the outbound transfer is never executed twice for the same request).
Response
string
The refund’s ID. Poll it via Get a refund.
string
The payment this refund belongs to.
string
Always the payment’s currency (any
currency sent in the body is ignored).string
The requested refund amount (decimal string).
string
Amount actually sent to the destination after deducting the network fee.
string
The refund’s own network fee, deducted from the amount sent.
string
The payer-supplied destination address.
string
Starts at
pending; advances to refunded (with txHash once verified on-chain) or failed — see Get a refund.Errors
Refunds are funded from your current available balance, not from a hold on the original payment — soINSUFFICIENT_FUNDS can occur even when the request is within the refundable cap (for example, after you withdrew the balance). Branch on the code, never on message text.
| Status | Code | Why |
|---|---|---|
| 400 | NOT_REFUNDABLE | The payment is not in a refundable (settled) state. |
| 404 | PAYMENT_NOT_FOUND | Unknown payment, or a payment belonging to another merchant. |
| 400 | INVALID_ID | Malformed paymentId. |
| 400 | INVALID_AMOUNT | amount is not a positive decimal. |
| 400 | EXCEEDS_REFUNDABLE | amount is above the net credited minus prior refunds. |
| 400 | NO_DESTINATION | Neither destinationAddress nor address was supplied. |
| 400 | INVALID_ADDRESS | Wrong-network or malformed payer address. |
| 400 | AMOUNT_BELOW_FEE | The amount does not exceed the refund’s network fee. |
| 400 | INSUFFICIENT_FUNDS | Your current available balance cannot fund the refund (see the note above). |
| 409 | SETTLED_STAMP_INCOMPLETE | Settled money is not fully stamped yet — retry shortly. |
| 409 | REFUND_FAILED_RETRY_NEW_KEY | The original refund on this idempotency key failed before broadcast — retry with a new key. |
| 400 | INVALID_IDEMPOTENCY_KEY | The key is not 1–255 printable ASCII characters (no spaces). |
| 400/401/403/429 | VERIFICATION_FAILED | JWT path: MFA missing, wrong, or locked out (same code, read the status). |
| 500 | REFUND_CREATE_FAILED / REFUND_EGRESS_FAILED | Server error creating or broadcasting the refund. |
| 503 | REFUND_EGRESS_UNKNOWN | The egress result is indeterminate — reconcile via Get a refund before retrying. |
FEE_ESTIMATE_FAILED, NO_SETTLEMENT_WALLET, and INVALID_DESTINATION.
See also
- List refunds for a payment: check the authoritative remaining-refundable amount before refunding.
- Get a refund: poll the returned
iduntil the refund reachesrefundedorfailed. - Send refund email code: request the
emailCodefactor for the dashboard (JWT) path.