Skip to main content
A refund sends money from a paid checkout back to the person who paid it. You choose how much (all of it or part of it), Platnova takes the money from your wallet and returns it to the payer the way that suits the payment method they used, and you get a webhook when it lands or fails.

How refunds work

  1. Create a refund with POST /v1/checkout/{checkout_id}/refund, giving a reason, an idempotency_key and, for a partial refund, an amount.
  2. We debit your wallet in the checkout’s currency and send the money back to the payer.
  3. Track it with the checkout.refunded and checkout.refund_failed webhooks, or fetch it with GET /v1/refunds/{refund_id}.

Which payments can be refunded

How the money goes back depends on how the payer paid, which is the checkout’s payment method (the same id values as the payment methods offered).
Crypto, Nova Tag (tag_transfer) and mobile money payments cannot be refunded through the API yet. A refund request for one of these is rejected with 422 refund_method_unsupported and nothing is debited. Refund these payers yourself for now, for example with Send payment.

How much you can refund

The refundable amount of a checkout is what you were credited for the payment, minus any refunds already made (refunds that failed do not count). What you were credited is the net amount, after Platnova’s processing fee. The processing fee is not returned when you refund. For example, if a payer paid NGN 10,000 and you were credited NGN 9,850 after a NGN 150 fee, you can refund up to NGN 9,850 in total. You can refund a checkout more than once, in parts, until the refundable amount reaches zero. Leave amount out to refund everything that is left.

Funding and fees

Refunds are paid from your available wallet balance in the checkout’s currency. If the balance does not cover the refund, the request is rejected with 422 insufficient_balance and nothing is debited.
A payment that is still waiting in a pending settlement is not in your wallet yet. Until it settles, refunding it draws on whatever else is in your balance.
  • Card, Apple Pay and Google Pay refunds: your wallet is debited the refund amount.
  • Bank-transfer refunds (bank_transfer, bank_account, ussd, opay): these go out through the same payout system as Send payment, so the normal payout fee and, where it applies, the stamp duty levy are charged on top of the refund. The payer receives exactly amount, and your wallet is debited amount plus those charges. Bank-transfer refunds need payouts to be enabled on your business; if they are not, the request is rejected with 422 payouts_not_enabled.
If a refund fails, everything it debited from your wallet, including any payout fee, is credited back, once.

Idempotency

Every refund request needs an idempotency_key: a string you choose, up to 128 characters, unique across your business. Use something tied to the refund in your own system, such as order-1042-refund-1.
  • Sending the same key for the same checkout and amount again returns the original refund with 200 and does not refund twice. If a request times out or you are not sure it arrived, retry it with the same key.
  • Sending the same key with a different checkout or amount is rejected with 409 idempotency_key_reused.
  • A repeated key returns the original refund whatever its status, including failed. To try again after a failed refund, send a new key.
  • A request refused with an error (see Errors) records nothing, so you can fix the cause and retry with the same key.
  • If the first request with a key is still being processed, a repeat gets 409 refund_in_progress. Retry shortly.

Statuses

succeeded and failed are final. A refund moves pending → processing → succeeded or failed; a refund the provider refuses straight away can be failed in the response to the create request itself, so check status there as well as listening for webhooks.

Authentication

Send your API key in the X-API-KEY header, as with the rest of the API. A request made with a dashboard session instead of an API key needs a team member who has the permission to send payments, and a completed two-factor check.

Approval for refunds to another account

A bank-transfer refund back to the account the customer paid from goes out straight away. A refund to a destination you choose is a payout to an account of your choosing, so your business’s payout approval rules apply, exactly as for sending a payment:
  • With an API key, it goes out straight away.
  • A team member who can approve payments sends it straight away.
  • A team member who cannot approve payments must name an approver. The refund answers 202 with awaiting_approval: true and status: "pending", and the approver is asked to approve the transfer. If they decline, the refund becomes failed and the money is returned to your wallet. Without an approver, the request is refused with 422 approver_required.

Create a refund

POST /v1/checkout/{checkout_id}/refund

Request fields

  • amount: Optional. How much to refund, in the checkout’s currency. Leave it out to refund the full refundable amount.
  • reason: Required, up to 255 characters. Why you are refunding. It is for your own records and systems and is not shown to the payer.
  • idempotency_key: Required, up to 128 characters. See Idempotency.
  • destination: Optional. An NGN bank account to send a bank-transfer refund to: account_number and bank_code. Used only for bank_transfer, bank_account, ussd and opay payments, and only when we do not have the account the payment came from. If we need it and it is missing, the request is rejected with 422 refund_destination_required. It is ignored for card refunds, and ignored when we hold the account the payment came from: the money goes back where it came from. A destination is checked with the customer’s bank before anything is sent.
  • approver: Optional. The ID of a teammate who can approve payments. Needed only when a team member who cannot approve payments refunds to a destination (see Approval).

Example request

With a destination account, for a bank-transfer payment whose source account we do not have:

Example response

The Refund object

List a checkout’s refunds

GET /v1/checkout/{checkout_id}/refunds returns every refund made on a checkout, newest first, including failed ones.

Retrieve a refund

GET /v1/refunds/{refund_id} returns a single Refund object, or 404 refund_not_found if there is no such refund on your business.

Errors

A refund request that cannot be made returns status: false with a machine-readable code in both error[0] and data.code. Branch on the code, not on message, which is written for people and may change.
None of these errors debits your wallet.

Webhooks

Two webhooks tell you how a refund ended. Subscribe to them in your webhook settings like any other event.

checkout.refunded

checkout.refund_failed

Use refund_id to match the webhook to the refund you created, and to ignore a repeated delivery of the same event.