Refunds

Send a settled payment's money back: to the payer's bank (NIP out, async) or their wallet (internal, instant). Partial allowed. Principal only; original fees aren't returned.

A refund moves a settled payment's money back toward whoever paid. Acute figures out where back means from the payment itself: a bank_transfer payment refunds to the payer's original bank account over NIP; a virtual_wallet payment refunds to the payer's wallet, internally. You just say how much.

# refund part of a payment
curl -X POST https://sandbox.api.acute.network/v1/payments/acuinf471028395610pay/refund \
  -H "Authorization: Bearer acuinf_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 200000,
    "reason": "Item out of stock"
  }'

Omit amount to refund the full remaining balance. Refund part of it and the payment goes partially_refunded; refund all of what was received and it goes refunded. You can only refund a payment that's settled or partial.

Refunds move principal only

A refund returns the money that landed in the wallet, not the fees the original payment charged. The fee on the original collection is not reversed. Budget for that: a fully-refunded ₦5,000.00 payment returns ₦5,000.00, and the ~₦75.00 of fees stays spent.

If the original payment was virtual_wallet, the refund is a plain internal move: debit the payment's target wallet, credit the payer's wallet, done. No rail, no fee, no waiting: the call returns completed.

Wallet refund

₦2,000.00 back to the payer's wallet

ledger_transaction kind: refund. Two legs, no fee leg: refunds move only the principal. Status is completed the moment it posts.
201 Created (wallet refund)
json
{
  "success": true,
  "statusCode": 201,
  "data": {
    "id": "acuinf662093417758rfd",
    "paymentId": "acuinf558712049336pay",
    "amount": 200000,
    "destination": "wallet",
    "status": "completed",
    "failureReason": null,
    "currency": "NGN",
    "createdAt": "2026-06-26T12:00:00.000Z",
    "completedAt": "2026-06-26T12:00:00.000Z"
  }
}

If the original payment was bank_transfer, the refund goes back out to the payer's original account: the snapshot we captured when the payment settled. That's a bank-rail disbursement, so it behaves exactly like a withdrawal: post the hold first, return processing, let the resolver confirm.

Bank refund hold (posted before the NIP call)

₦2,000.00 back to the payer's bank account

ledger_transaction kind: refund. Two legs into bank_outbound_suspense; the resolver clears it on confirmation or reverses it on failure (re-crediting the wallet).
201 Created (bank refund)
json
{
  "success": true,
  "statusCode": 201,
  "data": {
    "id": "acuinf662093417759rfd",
    "paymentId": "acuinf471028395610pay",
    "amount": 200000,
    "destination": "bank",
    "status": "processing",
    "failureReason": null,
    "currency": "NGN",
    "createdAt": "2026-06-26T12:00:00.000Z",
    "completedAt": null
  }
}

Wallet refunds skip straight to completed. Bank refunds wait in processing for the resolver, then land completed or failed, and a failed bank refund re-credits the wallet, just like a returned withdrawal.

processingentry status

Bank refunds start here: funds held in bank_outbound_suspense while the outbound NIP is in flight. (Wallet refunds skip this entirely.)

Transitions out

  • processingcompleted

    Resolver confirms the bank refund landed via verifyTransfer

    firesrefund.completed

  • processingfailed

    The rail fails the refund; the hold is reversed and the wallet re-credited

    firesrefund.failed

Wallet refunds go straight to completed

A virtual_wallet refund never enters processing: there's no rail to wait on, so the 201 already reads completed and refund.completed fires immediately. Only bank-destination refunds spend time in processing.

webhook: refund.completed
json
{
  "id": "acuinf445901327688evt",
  "type": "refund.completed",
  "createdAt": "2026-06-26T12:01:44.000Z",
  "data": {
    "id": "acuinf662093417759rfd",
    "paymentId": "acuinf471028395610pay",
    "amount": 200000,
    "destination": "bank",
    "status": "completed",
    "currency": "NGN"
  }
}

A refund changes the payment's status too. Refund part of what was received and the payment becomes partially_refunded; refund the whole received amount and it becomes refunded, firing payment.refunded. The refund's own refund.completed / refund.failed events track the individual refund; payment.refunded tracks the payment crossing fully-refunded.

Original paymentRefund destinationSettlesFailure behaviour
bank_transferbank (payer's account)async, via resolverreversing txn re-credits the wallet
virtual_walletwallet (payer's wallet)sync, instantn/a: posts atomically or errors