400 — “Request signature does not match”
The X-Signature you sent doesn’t match what Float computed over the body it received. In practice this is almost always one of:
You pretty-printed the body
You pretty-printed the body
Float verifies against a compact re-serialisation of your payload (key order preserved, whitespace stripped), so a pretty-printed body never matches. Serialise compactly, sign that exact string, and send that same string.The
400 response’s detail field includes the signature Float expected — compare it to yours: if they differ, your body bytes or key are wrong; if they match, you’re mangling the header itself.Wrong algorithm or encoding
Wrong algorithm or encoding
It must be HMAC-SHA512, output Base64-encoded (not hex, not URL-safe Base64). A hex digest is 128 characters; the correct Base64 digest is 88 characters ending in
=.Wrong key
Wrong key
Sign with the signing key, not the client secret — and check you’re using the sandbox key against sandbox and the production key against production.
Signing is sticky and you stopped signing
Signing is sticky and you stopped signing
Once Float has accepted one correctly signed request from your integration, unsigned mutating requests are rejected from then on. If you signed in a test script but your app doesn’t sign yet, the app’s requests will 400. Roll signing out everywhere at once. See Authentication.
Callback never arrives
1
Confirm the payment actually finished
Check the checkout with
GET /api/checkouts/:id. If it’s still received, the shopper hasn’t completed payment — there’s nothing to deliver yet.2
Check your endpoint is publicly reachable over HTTPS
curl -X POST https://your-site.com/float/notify -d '{}' -H "Content-Type: application/json" from outside your network. Localhost URLs, VPN-only hosts, and self-signed certificates all fail — TLS errors are a common silent killer, and Float alerts internally on SSL failures to your notify_url.3
Check your endpoint responds 2xx fast
Non-2xx responses and timeouts are retried up to 18 times over ~3 hours, then delivery stops. WAFs and bot protection (Cloudflare challenges) returning 403 to server-to-server POSTs are a frequent cause.
4
Reconcile via the API
If the retry window has passed, the state is still one
GET away — fetch the checkout and process it as you would the callback. Build this reconciliation as a scheduled job, not a manual step; see Accepting payments → Reconcile.Shopper wasn’t redirected back to my site
- The redirect uses the checkout’s
success_url/cancel_url— or theredirect_urlyour callback response returned. An empty or malformedredirect_urlin your callback response is ignored; a wrong one wins over your configured URLs. Log what your handler returns. - If your callback endpoint was down at completion time, Float still redirects the shopper using the URLs from the checkout — but your backend won’t know about the payment until a retry lands or you reconcile. Never gate the “thank you” page on your own database alone; treat
success_urlhits as a hint and confirm via callback or API.
422 validation errors
The same-day restriction only applies to integrations with automatic refund approval — manual-review integrations can submit immediately.
429 rate limits
The limits are listed per endpoint in the API overview. If you’re hitting them:
GET /api/config(5/hour) — cache it. It changes rarely; refresh hourly or on deploy.GET /api/checkouts/:id(5/minute) — you’re polling instead of using callbacks. Poll only the specific orders you’re reconciling.POST /api/refunds(1/second) — queue bulk refunds with a 1s+ spacing.
Try again in N seconds hint in the 429 body; don’t hammer.
Checkout link expired
The hosted payment session is valid for roughly 30 minutes. If a shopper returns to a stalepayment_url, create a new checkout (a fresh client_reference_id per attempt, or dedupe on your side) rather than reusing the old URL.
Getting help
Email support@float.co.za with:- The checkout ID (
data.idfrom create checkout) and yourclient_reference_id - The environment (sandbox or production) and timestamp (with timezone)
- The full request and response (redact your Bearer token)
- For callback issues: your
notify_urland your endpoint’s response/logs for the window
Float keeps a full audit trail of every callback attempt to your
notify_url (request and response), so support can tell you exactly what your endpoint returned and when.