SupaPayDevelopers

Integration Guide

Integrate SupaPay

Everything you need to go from sandbox keys to live payments.

Environments

EnvironmentBase URLKeysMoney moves?
Sandboxhttps://sandboxgateway.supapay.co.za/v1sp_test_…No — simulated
Productionhttps://gateway.supapay.co.za/v1sp_live_…Yes

Capability status

CapabilityStatusNotes
Hosted payment page (redirect)LiveCard + 3-D Secure
Card tokenisation (add-card session)AvailableSave cards for later
Charge saved cardsAvailableMerchant-initiated
Refunds (full & partial)AvailableAPI and dashboard
WebhooksAvailableHMAC-SHA256 signed
Embedded SDKProposedRoadmap
API ProxyMerchant PCIPCI 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…UseStatusPCI scope
Want to launch fast with minimal complianceHosted redirectAvailableSAQ A (lowest)
Need card fields inside your own checkoutEmbedded SDKProposedSAQ A
Are PCI DSS Level 1 certified and store cards yourselfAPI ProxyMerchant PCIFull PCI DSS

Not sure?

Start with the hosted redirect. You can add saved cards and webhooks later without changing your checkout.

Getting started

  1. Get your sandbox keys from the dashboard (or apply first).
  2. Send every request with Authorization: Bearer sp_test_…
  3. Send JSON bodies with Content-Type: application/json.
curl https://sandboxgateway.supapay.co.za/v1/webhook_endpoints \
  -H "Authorization: Bearer sp_test_4eC39HqLyjWDarjtT1"

Conventions

PrefixObject
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. Minimum 100.
  • 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.

HTTP header
Authorization: Bearer sp_test_4eC39HqLyjWDarjtT1
Publishable keys (sp_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.

HTTP header
Idempotency-Key: 7f9c2b1e-4a2d-4c1e-9d55-order-1001

Request 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 response
{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "message": "The card was declined.",
    "decline_code": "51",
    "param": null,
    "request_id": "req_4Gk2Yp8Lm1"
  }
}
HTTPCodeMeaning
400invalid_requestMalformed JSON or missing required parameter.
401authentication_failedMissing or invalid API key.
402card_declinedThe card was declined by the issuer.
403permission_deniedKey lacks permission for this resource or environment.
404resource_not_foundThe requested object does not exist.
409idempotency_conflictIdempotency-Key reused with a different request body.
422validation_failedParameters are well-formed but invalid (e.g. amount < 100).
429rate_limitedToo many requests. Respect Retry-After.
500internal_errorSomething went wrong on our side. Safe to retry with the same key.

Rate limits

TypeLimit
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. 1

    Create a deposit session

    Server-side, POST /deposit_sessions with amount, currency and return_url.

  2. 2

    Redirect the customer

    Send the browser to the returned redirect_url on pay.supapay.co.za.

  3. 3

    Customer pays

    They enter card details and complete 3-D Secure on the hosted page.

  4. 4

    Customer returns

    We redirect to your return_url?session_id=dps_….

  5. 5

    Confirm server-side

    Call GET /deposit_sessions/{id} or rely on the deposit.succeeded webhook. Never trust the redirect alone.

  6. 6

    Fulfil the order

    Mark the order paid once confirmed.

Webhooks

EventWhen
deposit.succeededPayment captured
deposit.failedPayment declined or abandoned
payment_method.attachedCard saved via add-card session
refund.succeededRefund completed
refund.failedRefund 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.

verify-webhook.js
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.