Skip to main content

Refunds

Create full or partial refunds for paid payments via the API. Refunds are processed through the payment provider; your merchant wallet is held while the refund is in flight.

Dashboard: merchants initiate refunds from Payments (requires email verification code).
API: server-to-server with an API key — no 2FA (Authentication).

Endpoints

MethodPathScope
POST/api/transactions/{id}/refund/refunds:write or payments:write
GET/api/refunds/refunds:read or payments:read
GET/api/refunds/{id}/refunds:read or payments:read

Use the same base URL as Create payment (/api/ or /sandbox/api/ for sandbox keys).

note

POST /api/refunds/ is for the merchant dashboard only. API keys receive 405 — always refund via POST /api/transactions/{id}/refund/.

Create a refund

POST /api/transactions/<payment_id>/refund/
Authorization: Api-Key <your_api_key>
Content-Type: application/json

payment_id — Public ID (e.g. TXabc123) or numeric transaction ID (Get payment by id).

Request body

{
"reason": "Customer request",
"amount": "40.00"
}
FieldRequiredDescription
reasonYesShown to the customer and sent to the provider (max 500 characters)
amountNoPartial refund in charge currency (TTC). Omit to refund the full remaining refundable amount

Response (200)

{
"status": "processing",
"refund_id": 42,
"refund_public_id": "xYz9AbC",
"transaction_id": 17,
"amount": "40.00",
"currency": "USD",
"amount_usd": "39.20",
"amount_in_cents": 4000
}

On success the refund is processing while the provider completes it. Subscribe to refund.status_changed for lifecycle updates.

Example

curl -X POST "https://api.sandbox.nd8.com/api/transactions/TXabc123/refund/" \
-H "Authorization: Api-Key YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Customer request", "amount": "40.00"}'

List refunds

GET /api/refunds/
Authorization: Api-Key <your_api_key>

Query parameters

ParameterDescription
statusFilter: pending, processing, completed, failed, rejected
searchSearch payment ID or reason
created_at__gteISO datetime — created on or after
created_at__lteISO datetime — created on or before
orderinge.g. -created_at, amount
page / page_sizePagination

Response (200)

Array or paginated object with refund rows:

[
{
"id": "RFxYz9AbC",
"transaction_id": "TXabc123",
"amount": "40.00",
"currency": "USD",
"amount_in_cents": 4000,
"amount_usd": "39.20",
"reason": "Customer request",
"status": "processing",
"created_at": "2026-03-01T12:00:00Z",
"updated_at": "2026-03-01T12:00:05Z"
}
]

Get refund by ID

GET /api/refunds/<id>/
Authorization: Api-Key <your_api_key>

id — Public refund ID (e.g. RFxYz9AbC) or numeric ID.

Eligibility rules

Refunds are allowed when all of the following hold:

  1. Payment status is paid or refund_pending (partial refund in progress).
  2. Provider has not marked the payment FULLY_REFUNDED or PARTIAL_REFUNDED (no further refunds after provider completion).
  3. Remaining refundable amount on the payment is > 0 (prior refunds reduce the cap).
  4. Merchant wallet can cover the refund — you may issue a partial refund up to the lesser of remaining payment amount and wallet-available amount (balance + rolling reserve, proportional to net settlement).

There is no time limit on when a refund can be requested.

Common 400 errors:

Message (example)Cause
Refund amount exceeds remaining…Partial amount above payment remaining
Refund amount exceeds wallet-available limit…Amount above what wallet can cover — use a lower partial refund
Insufficient wallet balance to refund any amount…Wallet empty while payment still has remaining refundable gross
A refund is already in progressPending/processing refund exists
Payment has already been refundedProvider or platform status blocks new requests

Refund status lifecycle

StatusMeaning
pendingRecord created, provider request not yet confirmed
processingAccepted by provider; awaiting completion
completedRefund settled; customer charged back
failedProvider rejected or sync reported failure; wallet hold released
rejectedManually rejected (internal/admin)

Status is synced from the provider (webhooks + periodic poll). You do not need to poll if you handle webhooks.

Webhooks

Configure Webhooks to receive:

  • refund.status_changed — refund processing, completed, or failed (recommended for refund tracking).
  • transaction.status_changed — parent payment may move to refund_pending or refunded.

Example handler (Node.js):

app.post('/webhooks/iflowed', express.raw({ type: 'application/json' }), (req, res) => {
const event = req.header('X-Webhook-Event');
const body = JSON.parse(req.body.toString());

if (event === 'refund.status_changed') {
console.log(body.refund_id, body.status, body.amount, body.transaction_id);
// Fulfill order reversal, CRM update, etc.
}

res.sendStatus(200);
});

API vs dashboard

API keyDashboard (Token session)
AuthAuthorization: Api-Key …Authorization: Token …
2FANot requiredEmail verification code required
Create refundPOST /transactions/{id}/refund/Payments → View → Refund
List refundsGET /refunds/Refunds page