> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kwugwo.africa/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a refund

> Refund all or part of a successful charge.

Refunds money from a successful [activity](https://docs.kwugwo.africa/concepts.md#activity-how-an-ugwo-gets-charged)
back to the payer. The refund is submitted to the provider before the
call returns, so `status` comes back as `processing`, or `failed` if
the provider refused it outright.

Read [Refunds](https://docs.kwugwo.africa/refunds/overview.md) first, in particular [how much you
can refund](https://docs.kwugwo.africa/refunds/overview.md#how-much-you-can-refund) and
[idempotency](https://docs.kwugwo.africa/refunds/overview.md#idempotency).

> [!NOTE]
>
> The activity must be `successful` or `partially_refunded`. A failed
> charge, one still waiting on the customer, or a fully `refunded`
> one is rejected with `405`.

## Body parameters

**ugwo\_activity** `string` *required*

The activity to refund, `act.XXXX.YYYY...`. Must belong to your
workspace and hold settled money.

**amount** `integer`

Amount to refund, in the **smallest currency unit** (kobo for NGN).
Omit it to refund everything still refundable on the activity.

Must be at least the currency minimum (`20000` = ₦200.00 for NGN),
and cannot take the total past what the activity collected.

**ref** `string`

Your own reference, 1-150 characters. **Unique per workspace**, so
reusing one returns `409`. Strongly recommended on every refund:
it is your idempotency key.

**reason** `string`

Why you're refunding, 1-255 characters. Forwarded to the provider
as the merchant note, so it shows up in their dashboard too.

**nzube\_psp** `string`

Provider connection to refund through, `psp.XXXX.YYYY...`. Defaults
to the connection that took the payment. It must be active and
belong to the same provider as the original charge.

**destination** `object`

The customer's bank account to send the money to. **Required by some
PSPs for `bank_transfer` and `ussd` payments**, where the original
payer account isn't known; without it the refund fails. See
[Refunds for bank transfer and USSD payments](https://docs.kwugwo.africa/refunds/overview.md#refunds-for-bank-transfer-and-ussd-payments).

**properties**

**provider** `string` *required*

[Bank code](https://docs.kwugwo.africa/concepts.md#bank-codes-nigeria), e.g. `gtb`.

**account\_number** `string` *required*

Exactly 10 digits.

**meta** `object`

Up to 50 string key/value pairs (keys ≤ 100 chars, values ≤ 255;
`null` values are dropped). Stored against the refund for your own
bookkeeping. The merchant API does not return it.

## Response

The [Refund object](https://docs.kwugwo.africa/refunds/object.md). The fields
worth branching on immediately:

**uid** `string`

Refund ID, `rfd.XXXX.YYYY...`. Persist it: every refund webhook
identifies itself with this.

**status** `string`

`processing` on the happy path, `failed` if the provider refused it
outright. Do **not** treat `processing` as money returned; wait for
[`refund.successful`](https://docs.kwugwo.africa/refunds/webhooks).

**error** `object | null`

Populated when `status` is `failed`.

**ugwo\_activity** `object`

The activity, already updated. Read `refunded_amount` here to see
how much of the payment is now committed to refunds.

## Errors

| Status | Cause |
| - | - |
| `405` | The refund isn't allowed in the payment's current state: the activity never succeeded, the `amount` is below the minimum or above what's left, or the provider connection is inactive or belongs to a different provider. The `message` says which. |
| `409` | A refund with this `ref` already exists on the workspace. |
| `422` | `ugwo_activity` unknown, `amount` below `1`, `ref` or `reason` too long, or malformed `meta`. |
| `500` | Unexpected failure. The refund may or may not exist, so [list refunds](https://docs.kwugwo.africa/refunds/list.md) filtered by the activity before retrying. |

> [!WARNING]
>
> A `failed` refund is a **`200` response**, not an HTTP error. A
> provider declining a refund is a normal outcome, so it comes back
> as a refund object with `"status": "failed"` and a populated
> `error`. Check `status`, not just the HTTP code.

```bash cURL theme={"system"}
curl https://api.kwugwo.africa/v1/refunds \
  -H "Authorization: Bearer $KWUGWO_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ugwo_activity": "act.VCvr.qnbBJaElzSTHOcaTzxbuDlIs",
    "amount": 200000,
    "ref": "order_4719_refund_1",
    "reason": "Customer returned the notebook",
    "meta": { "order_id": "4719", "agent": "ada@shop.example" }
  }'
```

```js Node theme={"system"}
const res = await fetch("https://api.kwugwo.africa/v1/refunds", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.KWUGWO_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    ugwo_activity: activityUid,
    amount: 200000,                    // omit to refund everything left
    ref: `${orderId}_refund_1`,        // idempotency key
    reason: "Customer returned the notebook",
  }),
});

const refund = await res.json();

if (res.status === 409) {
  // Already refunded with this ref: safe to ignore on a retry.
} else if (refund.status === "failed") {
  console.error(refund.error.code, refund.error.message);
}
```

```php PHP theme={"system"}
<?php
$ch = curl_init("https://api.kwugwo.africa/v1/refunds");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('KWUGWO_SECRET_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'ugwo_activity' => $activityUid,
        'amount'        => 200000,
        'ref'           => $orderId . '_refund_1',
        'reason'        => 'Customer returned the notebook',
    ]),
]);
$refund = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```python Python theme={"system"}
import os, requests

res = requests.post(
    "https://api.kwugwo.africa/v1/refunds",
    headers={"Authorization": f"Bearer {os.environ['KWUGWO_SECRET_KEY']}"},
    json={
        "ugwo_activity": activity_uid,
        "amount": 200000,
        "ref": f"{order_id}_refund_1",
        "reason": "Customer returned the notebook",
    },
)

refund = res.json()
if refund.get("status") == "failed":
    print(refund["error"]["code"], refund["error"]["message"])
```

```go Go theme={"system"}
body, _ := json.Marshal(map[string]any{
    "ugwo_activity": activityUid,
    "amount":        200000,
    "ref":           orderId + "_refund_1",
    "reason":        "Customer returned the notebook",
})

req, _ := http.NewRequest("POST", "https://api.kwugwo.africa/v1/refunds", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("KWUGWO_SECRET_KEY"))
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
```

```json 200 OK (accepted) theme={"system"}
{
  "uid": "rfd.VCvr.Hs7dQmXk2LpRvN9tYbCwZaEf",
  "psp_external_ref": "3018284",
  "ref": "order_4719_refund_1",
  "reason": "Customer returned the notebook",
  "amount": 200000,
  "status": "processing",
  "error": null,
  "created_at": "2026-08-12T10:04:11+00:00",
  "updated_at": "2026-08-12T10:04:12+00:00",
  "ugwo": {
    "uid": "ugw.VCvr.7K2qPmRtV9xLnQ8sD1cYwHfE",
    "currency": "NGN"
  },
  "ugwo_activity": {
    "uid": "act.VCvr.qnbBJaElzSTHOcaTzxbuDlIs",
    "activity_type": "charge",
    "status": "partially_refunded",
    "psp": "paystack_ng",
    "psp_external_id": "5kfxw34ugz",
    "amount": 500000,
    "charged_amount": 500000,
    "refunded_amount": 200000,
    "error": null,
    "created_at": "2026-08-11T19:55:50+00:00",
    "updated_at": "2026-08-12T10:04:11+00:00"
  },
  "nzube_psp": null
}
```

```json 200 OK (PSP refused) theme={"system"}
{
  "uid": "rfd.VCvr.Pq3vLmTn8XcBd5RyWkJhZsAu",
  "psp_external_ref": null,
  "ref": "order_4719_refund_2",
  "reason": "Duplicate charge",
  "amount": 100000,
  "status": "failed",
  "error": {
    "code": "psp_error",
    "message": "Transaction has been fully refunded"
  },
  "created_at": "2026-08-12T10:09:02+00:00",
  "updated_at": "2026-08-12T10:09:03+00:00",
  "ugwo": {
    "uid": "ugw.VCvr.7K2qPmRtV9xLnQ8sD1cYwHfE",
    "currency": "NGN"
  },
  "ugwo_activity": {
    "uid": "act.VCvr.qnbBJaElzSTHOcaTzxbuDlIs",
    "activity_type": "charge",
    "status": "partially_refunded",
    "psp": "paystack_ng",
    "psp_external_id": "5kfxw34ugz",
    "amount": 500000,
    "charged_amount": 500000,
    "refunded_amount": 200000,
    "error": null,
    "created_at": "2026-08-11T19:55:50+00:00",
    "updated_at": "2026-08-12T10:09:03+00:00"
  },
  "nzube_psp": null
}
```

```json 405 Not Allowed theme={"system"}
{
  "message": "nzube.refund.error.refund_amount_too_high",
  "code": "not_allowed",
  "params": []
}
```

```json 409 Conflict theme={"system"}
{
  "message": "nzube.refund.error.ref_already_exists",
  "code": "already_exists",
  "params": []
}
```

```json 422 Validation Failed theme={"system"}
{
  "type": "validation.error",
  "title": "An error occurred",
  "status": 422,
  "detail": "This value should be 1 or more.",
  "message": "Validation failed",
  "errors": [
    { "field": "amount", "message": "This value should be 1 or more." }
  ]
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.