Prerequisites
- The original transfer is in
completedstate - You’re still within the refund window (default 90 days from transfer completion — configurable per tenant)
- The sending account still has sufficient balance, OR you use a different funding source
Full refund
Simplest case — return the full amount:amount → full refund.
Partial refund
Specify the amount to return:Reason codes
| Reason | When to use |
|---|---|
duplicate_payment | You accidentally charged twice |
service_not_rendered | Buyer paid but didn’t receive |
customer_request | Voluntary goodwill refund |
error | System / integration bug |
other | Anything else (provide reasonDetails) |
Idempotency
Always setX-Idempotency-Key. Refunds are the one operation you absolutely do not want to accidentally double-process.
The key is cached 24 hours. Same key + same parameters → returns original result. Same key + different parameters → IDEMPOTENCY_KEY_REUSED.
Watch settlement
pending → completed | failed.
Or subscribe to webhooks: transfer.refunded fires on the original transfer when refund settles.
Failures
| Error | Cause | Fix |
|---|---|---|
INSUFFICIENT_BALANCE | Originating wallet no longer has funds | Top up or use a different source account |
REFUND_WINDOW_EXPIRED | Past the 90-day (or tenant-configured) window | Out of band — contact support with strong justification |
TRANSFER_NOT_FOUND | Wrong transfer ID or already fully refunded | Check originalTransferId |
STATE_TRANSITION_INVALID | Transfer not in completed state | Only completed transfers are refundable |
AMOUNT_EXCEEDS_REMAINING | Sum of partial refunds would exceed original | Reduce amount or refund balance |
List refunds
Filter by status, account, or date range:- Stuck pending — refunds older than 1 hour still
pending - Recent failures — indicate wallet or rail issues upstream
- Volume trend — sudden spikes often precede disputes
Refund vs. dispute
- Refund = you voluntarily return money. Use when the buyer asks or you spot an error first.
- Dispute = buyer filed a formal complaint via card network or Sly. You can still issue a refund during a dispute — often the cleanest resolution.
Endpoints
| Endpoint | Purpose |
|---|---|
POST /v1/refunds | Initiate refund |
GET /v1/refunds | List refunds |
GET /v1/refunds/:id | Refund detail |
Practice
In sandbox: send a small transfer, then immediately refund it. Check that your webhook handler receivestransfer.refunded and updates your ledger. Test partial refunds, double-refund attempts, and post-window rejection before going live.