Integration Guide
Integrate SupaPay
Everything you need to go from sandbox keys to live payments.
Environments
| Environment | Base URL | Keys | Money moves? |
|---|---|---|---|
| Sandbox | https://sandboxgateway.supapay.co.za/v1 | sp_test_… | No — simulated |
| Production | https://gateway.supapay.co.za/v1 | sp_live_… | Yes |
Capability status
| Capability | Status | Notes |
|---|---|---|
| Hosted payment page (redirect) | Live | Card + 3-D Secure |
| Card tokenisation (add-card session) | Available | Save cards for later |
| Charge saved cards | Available | Merchant-initiated |
| Refunds (full & partial) | Available | API and dashboard |
| Webhooks | Available | HMAC-SHA256 signed |
| Embedded SDK | Proposed | Roadmap |
| API Proxy | Merchant PCI | PCI DSS attestation required |
Overview
The SupaPay API lets you:
- Tokenise cards securely via a hosted session
- Charge saved cards for repeat purchases
- Authenticate cardholders with 3-D Secure
- Refund deposits in full or in part
- Receive real-time, signed webhooks
Integration options — which one applies to you?
| If you… | Use | Status | PCI scope |
|---|---|---|---|
| Want to launch fast with minimal compliance | Hosted redirect | Available | SAQ A (lowest) |
| Need card fields inside your own checkout | Embedded SDK | Proposed | SAQ A |
| Are PCI DSS Level 1 certified and store cards yourself | API Proxy | Merchant PCI | Full PCI DSS |
Not sure?
Getting started
- Get your sandbox keys from the dashboard (or apply first).
- Send every request with
Authorization: Bearer sp_test_… - Send JSON bodies with
Content-Type: application/json.
curl https://sandboxgateway.supapay.co.za/v1/webhook_endpoints \
-H "Authorization: Bearer sp_test_4eC39HqLyjWDarjtT1"Conventions
| Prefix | Object |
|---|---|
cust_ | Customer |
pm_ | Payment method |
dps_ | Deposit session |
dep_ | Deposit |
ref_ | Refund |
whe_ | Webhook endpoint |
evt_ | Event |
- Amounts are integers in minor units (cents). R150.00 =
15000. Minimum100. - Currencies are ISO 4217 codes, e.g.
ZAR. - Timestamps are RFC 3339 in UTC, e.g.
2026-10-30T09:15:00Z.
Authentication
Authenticate with your secret key as a Bearer token. Sandbox keys only work against the sandbox base URL, and live keys only against production.
Authorization: Bearer sp_test_4eC39HqLyjWDarjtT1sp_pk_test_, sp_pk_live_) cannot call server endpoints and will return 401.Idempotency
Send an Idempotency-Key header (e.g. a UUID) on every POST. Retrying with the same key returns the original result instead of creating a duplicate. Keys are retained for 24 hours.
Reusing a key with a different body returns 409 idempotency_conflict.
Idempotency-Key: 7f9c2b1e-4a2d-4c1e-9d55-order-1001Request tracing
Every response includes an X-Request-Id header. Log it, and include it when contacting support so we can find your request instantly.
Error handling
Errors return a non-2xx status and a consistent JSON envelope:
{
"error": {
"type": "card_error",
"code": "card_declined",
"message": "The card was declined.",
"decline_code": "51",
"param": null,
"request_id": "req_4Gk2Yp8Lm1"
}
}| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed JSON or missing required parameter. |
| 401 | authentication_failed | Missing or invalid API key. |
| 402 | card_declined | The card was declined by the issuer. |
| 403 | permission_denied | Key lacks permission for this resource or environment. |
| 404 | resource_not_found | The requested object does not exist. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different request body. |
| 422 | validation_failed | Parameters are well-formed but invalid (e.g. amount < 100). |
| 429 | rate_limited | Too many requests. Respect Retry-After. |
| 500 | internal_error | Something went wrong on our side. Safe to retry with the same key. |
Rate limits
| Type | Limit |
|---|---|
| Reads (GET) | 200 requests / minute |
| Writes (POST, DELETE) | 60 requests / minute |
Exceeding a limit returns 429 with a Retry-After header (seconds). Back off and retry.
Hosted redirect flow
- 1
Create a deposit session
Server-side,
POST /deposit_sessionswith amount, currency andreturn_url. - 2
Redirect the customer
Send the browser to the returned
redirect_urlon pay.supapay.co.za. - 3
Customer pays
They enter card details and complete 3-D Secure on the hosted page.
- 4
Customer returns
We redirect to your
return_url?session_id=dps_…. - 5
Confirm server-side
Call
GET /deposit_sessions/{id}or rely on thedeposit.succeededwebhook. Never trust the redirect alone. - 6
Fulfil the order
Mark the order paid once confirmed.
Webhooks
| Event | When |
|---|---|
deposit.succeeded | Payment captured |
deposit.failed | Payment declined or abandoned |
payment_method.attached | Card saved via add-card session |
refund.succeeded | Refund completed |
refund.failed | Refund could not be processed |
Each request carries a SupaPay-Signature header: an HMAC-SHA256 of {timestamp}.{raw_body} using your endpoint secret. Verify it and reject events older than 5 minutes. Respond 2xx within 10 seconds; we retry with exponential backoff for up to 3 days.
import crypto from "node:crypto";
export function verifySupaPay(rawBody, header, secret) {
// header: "t=1761815700,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e7..."
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const signed = `${parts.t}.${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return ok && fresh;
}Testing in sandbox
The sandbox simulates authorisations, 3-D Secure challenges and declines. Use our sandbox test cards or try requests in the playground.
Integration checklist
Support
Dedicated developer support will be available right here in the portal once you're onboarded — it's coming soon. Until then, questions can go through our contact form; include your X-Request-Id if it relates to a specific request.
