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

# Payment callback

> The signed webhook Float POSTs to your notify_url when a payment finishes.

When a checkout reaches a final state, Float sends a single `POST` to the `notify_url` you supplied on [create checkout](/api-reference/create-checkout). This page is the wire contract; the [callbacks guide](/guides/callbacks) covers handler implementation, verification code, and local testing.

## Request

**Method:** `POST` · **Content-Type:** `application/json`

| Header         | Value                                                                                 |
| -------------- | ------------------------------------------------------------------------------------- |
| `Content-Type` | `application/json`                                                                    |
| `X-Signature`  | `Base64( HMAC-SHA512( signing_key, raw_body ) )` — verify before trusting the payload |

### Body

```json theme={null}
{
  "status": "successful",
  "amount": 249900,
  "currency": "ZAR",
  "client_reference_id": "order-1001",
  "number_of_payments": 3
}
```

<ResponseField name="status" type="string" required>
  `successful` or `cancelled`. There is no `failed` — the shopper can retry on the hosted page until they succeed or abandon, so a failed attempt is not final.
</ResponseField>

<ResponseField name="amount" type="integer" required>
  The full payable amount in cents, exactly as sent when the checkout was created.
</ResponseField>

<ResponseField name="currency" type="string" required>
  ISO 4217 code (`ZAR`), as sent on the checkout.
</ResponseField>

<ResponseField name="client_reference_id" type="string" required>
  Your reference from the checkout request, echoed verbatim. Use this to find the order on your side.
</ResponseField>

<ResponseField name="number_of_payments" type="integer" required>
  The instalment term the shopper selected on the payment page (`1` = once-off).
</ResponseField>

<Note>
  The callback does **not** include the checkout ID. Store the mapping `client_reference_id → checkout_id` when you create the checkout — you'll need the ID for [status lookups](/api-reference/get-checkout) and [refunds](/api-reference/create-refund).
</Note>

## Your response

Respond `2xx` quickly (do fulfilment work async). Any `2xx` counts as delivered, but returning JSON unlocks an optional feature:

<ResponseField name="redirect_url" type="string (url)">
  If present and non-empty in your JSON response, Float sends the shopper's browser here instead of the checkout's `success_url` / `cancel_url` — useful for order-specific confirmation pages created during callback handling.
</ResponseField>

```json theme={null}
{ "redirect_url": "https://example.com/orders/1001/confirmation" }
```

Return `{}` (or anything else with a `2xx`) to use the default redirect.

## Delivery semantics

| Property              | Behaviour                                                                                |
| --------------------- | ---------------------------------------------------------------------------------------- |
| Trigger               | Checkout transitions to `successful` or `cancelled`                                      |
| Timing                | Synchronous with the shopper finishing payment — typically seconds                       |
| Retries               | Failed deliveries retry up to **18 times over \~3 hours** (10-minute intervals)          |
| Retry trigger         | Network errors and non-2xx responses; `429` is retried with respect for the backoff      |
| Ordering & duplicates | Treat the callback as **at-least-once**: deduplicate on `client_reference_id` + `status` |
| Timeout               | Keep your handler under a few seconds; respond before doing heavy work                   |

<Warning>
  Callbacks can be missed (your endpoint down for longer than the retry window). Reconcile any order still pending after \~1 hour with [`GET /api/checkouts/:id`](/api-reference/get-checkout) — never leave paid orders unfulfilled because a webhook was dropped.
</Warning>
