The Refunds API lets you return money for a payment your customer has already made. One endpoint, POST /payments/v1/refunds, handles every refund, but what it does depends on how the customer paid. For a card payment, BlinkPay sends the money back through the card network. For a bank (account-to-account) payment, BlinkPay does not move any money: it gives you the account number the customer paid from, and you pay them back yourself.
Refunds must be enabled for your merchant account, and card refunds are enabled separately from bank refunds. If a refund returns BP047 or BP049, contact support@blinkpay.co.nz to have them enabled.
| Endpoint | Scope | Purpose |
|---|---|---|
POST /payments/v1/refunds | create:refund | Create a refund |
GET /payments/v1/refunds/{refund_id} | view:refund | Check a refund's status |
GET /payments/v1/payments/{payment_id} | view:payment | See every refund on a payment in refunds[] |
Each refund has a type. Which types you can use depends on the payment method:
| Refund type | Bank (account-to-account) payment | Card payment |
|---|---|---|
account_number | Supported. No money moves: the refund is completed at once and carries the payer's account number, so you can pay them back | Not supported (BP013) |
full_refund | Not supported (BP048) | Supported. Returns the whole payment through the card network, or cancels the charge if it has not gone out yet |
partial_refund | Not supported (BP048) | Supported. Returns the amount you specify through the card network |
Refunds are not limited by consent type: a payment made under a quick payment, a single consent or an enduring consent can be refunded in the same way. The payment must have reached AcceptedSettlementCompleted first; until then a refund returns BP012. A refund can never take the total refunded for a payment above the amount charged for it (BP039).
Card refunds must be made within 60 days of the payment completing; after that the API refuses them with BP045 and you need to contact BlinkPay support. account_number refunds have no time limit.
On a surcharged payment the customer was charged total_charge, your price plus the surcharge, and you can refund up to that total, surcharge included. On a card payment, a full_refund returns the whole of total_charge and partial_refund amounts can add up to it. On a bank payment you pay the customer back yourself, so a full refund means paying back total_charge.
Always send an idempotency-key header with a UUID you generate for this refund. See Idempotency and Retries for why it matters more here than anywhere else.
The request carries only the payment ID and the type.
POST /payments/v1/refunds HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}
Content-Type: application/json
idempotency-key: 0f8a2c4e-6b1d-4f3a-8e9c-7d5b3a1f2e4c
{
"type": "account_number",
"payment_id": "7c1e4b2a-9d3f-4e8a-b6c5-2f1a0d9e8c7b"
}
Asking again for the same payment returns the existing refund rather than a new one.
pcr is required. It is stored with the refund for your records.
POST /payments/v1/refunds HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}
Content-Type: application/json
idempotency-key: 3b9d1e7f-2a4c-4d8b-9f6e-1c5a7b3d9e2f
{
"type": "full_refund",
"payment_id": "a4f2c8e1-5b7d-4a9c-8e3f-6d1b2c9a7e5f",
"pcr": {
"particulars": "Refund",
"code": "ORDER1234",
"reference": "Returned"
}
}
amount and pcr are both required. The amount uses the same format as payment amounts (a string with one or two decimal places, greater than zero) and its currency must match the payment's.
POST /payments/v1/refunds HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}
Content-Type: application/json
idempotency-key: 8e4a6c2b-1d3f-4b5e-a7c9-2f0d8b6e4a1c
{
"type": "partial_refund",
"payment_id": "a4f2c8e1-5b7d-4a9c-8e3f-6d1b2c9a7e5f",
"amount": {
"currency": "NZD",
"total": "10.00"
},
"pcr": {
"particulars": "Refund",
"code": "ORDER1234",
"reference": "1 item"
}
}
Response (201 Created) for every type:
{
"refund_id": "5e2d8f1c-3b4a-4c6d-9e7f-0a1b2c3d4e5f"
}
A 201 means the refund was accepted, not that the money has been returned. Check its status as described below.
| Status | Meaning |
|---|---|
processing | A card refund has been sent to the card processor and is not yet confirmed. Keep polling. |
completed | For an account_number refund: the account number is available and no money has moved. For a card refund: the card processor has accepted the refund, or the charge was cancelled before it went out. |
failed | The card processor refused the refund. No money moved, and the amount is available to refund again with a new idempotency key. |
completed and failed are terminal. On a card refund, completed means the refund has left your side; it usually reaches the customer within a few business days.
GET /payments/v1/refunds/{refund_id} HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}
Response for an account number refund:
{
"refund_id": "5e2d8f1c-3b4a-4c6d-9e7f-0a1b2c3d4e5f",
"status": "completed",
"creation_timestamp": "2026-10-07T10:15:00+13:00",
"status_updated_timestamp": "2026-10-07T10:15:00+13:00",
"account_number": "12-3456-7890123-00",
"detail": {
"type": "account_number",
"payment_id": "7c1e4b2a-9d3f-4e8a-b6c5-2f1a0d9e8c7b"
}
}
Pay the customer back to account_number from your own bank account. The account number is personal information: store it only as long as you need it and keep it out of logs.
For a card refund, account_number is N/A and detail repeats the request, including amount for a partial refund. A card refund is usually completed within seconds, but a refund can stay processing for days, so don't wait on it: initiate it, then check its status later, in the Client Portal or with this endpoint from a daily sweep. The same refunds also appear in the refunds array of GET /payments/v1/payments/{payment_id}.
GET /payments/v1/quick-payments/{quick_payment_id} shows consent.payments[0].status as AcceptedSettlementCompleted with payment_id a4f2c8e1-5b7d-4a9c-8e3f-6d1b2c9a7e5f. Store the payment_id against the order.10.00 with that UUID as the idempotency-key.201 with a refund_id. Store it.GET /payments/v1/refunds/{refund_id} later, for example from a daily sweep. It shows processing until the card processor confirms it, then completed.refund_id back and the customer is not refunded twice.partial_refund for 39.99 with a new key. A full_refund at this point would be refused with BP039, because it always claims the whole payment.For a bank payment the flow is shorter: send an account_number refund, read account_number from the completed refund, and make the payment to the customer yourself.
For a card full_refund, BlinkPay checks with the card processor and chooses how to return the money. If the charge has not gone out yet and no refund has been attempted on the payment, it is cancelled and the customer sees no charge, although their bank may show a pending amount until it releases it. Otherwise the money comes back as a separate refund after the original charge. A partial_refund is always a separate refund. You cannot choose between the two; see Card Payments for what the customer sees.
A cancellation made in your card processor's portal appears as a completed full_refund on the payment. Other refunds made outside BlinkPay don't appear in the API.
Send an idempotency-key header (a UUID) with every refund and keep it until you know the outcome.
| Situation | What happens | What to do |
|---|---|---|
| Same key, same body, first request finished | The original 201 and refund_id are returned; nothing new is refunded | Use the returned refund_id |
| Same key, same body, first request still in progress | 409 BP711 | Wait a few seconds and retry with the same key |
| Same key, different body | 409 BP702 | Use a new key for a different refund |
No response (timeout, network error, 5xx) | Unknown | Retry with the same key |
Refund failed | The key stays tied to the failed refund; reusing it returns the failed refund_id | Retry with a new key |
409 BP715 (a card charge settled while it was being cancelled) | Nothing was refunded | Retry with a new key; the retry is sent as a refund |
Without a key, a retried partial_refund refunds the customer twice, up to the amount charged.
A 502 (BP903 to BP908) means the card processor's answer did not arrive, so the refund may or may not have gone through. Check the payment's refunds[] before doing anything else, and if you retry, use the same key.
A refund that can't be made is refused with a 422 and a code saying why: refunds aren't enabled for your account, the refund type doesn't match the payment method, the payment hasn't completed, the amount is more than is left to refund, or a card payment is over 60 days old. Show the reason to your operator and don't retry unchanged. The codes are listed under Refund Errors.
Store the payment_id against every order when the payment completes, because you need it to refund; you can also find it later from the consent, whose payments[] lists every payment with its payment_id. Generate the idempotency key when you decide to refund, store it with the refund request, and reuse it for every retry of that refund until you have a refund_id and a terminal status. Treat completed on an account_number refund as the start of your own payment to the customer, not the end of the refund, and keep a record of when you paid them back. Reconcile partial refunds against the running total so you never ask for more than is left.
Refunds work in the sandbox the same way as in production, provided refunds are enabled on your sandbox account. Make a payment with the sandbox test values, wait for AcceptedSettlementCompleted, then refund it. You can also produce the main refusals: refund a payment that has not completed (BP012), send partial_refund on a bank payment (BP048), or refund more than the payment (BP039).