Guides Errors v1.0

Build Payments
That Scale Globally

The SnapPay API enables you to integrate secure, PCI DSS Level 1 compliant card payment processing into any application, in any language, anywhere in the world.

99.9% uptime SLA
REST / JSON API
10+ languages
PCI DSS Level 1
All API requests must be made over HTTPS. Requests made over plain HTTP will fail. Your API key must be kept confidential — never expose it in client-side code.

Authentication

SnapPay uses API keys to authenticate requests. You can view and manage your API keys in the SnapPay Dashboard. Every API request requires three credentials sent as JSON body parameters.

api_key required

Your secret API key. Found in your merchant dashboard under Developer → API Keys. Format: spay_live_xxxxxxxxxxxxxxxxxxxx for production, spay_test_xxxxxxxxxxxxxxxxxxxx for sandbox.

business_id required

Your unique business identifier (integer). A merchant account may have multiple business IDs for different stores, brands, or regions.

Never share your secret api_key publicly or commit it to version control. Use environment variables (SNAPPAY_API_KEY) and rotate keys immediately if compromised.

Base URL

All API endpoints are relative to the following base URLs depending on environment:

Production

BASE URL https://api.usesnappay.com/v1

Local / Development

LOCAL https://sandbox.usesnappay.com
All test API keys prefixed with spay_test_ are automatically routed to the sandbox environment. No real charges are made.

Versioning

SnapPay API versioning is controlled via the URL prefix /v1. We guarantee backward compatibility within the same major version.

v1.0 2026-03-01 Initial release — Payment Check, Auth, Transactions Current

Payment Check

Verify the status of a card payment transaction by its developer-assigned transaction_id. Use this endpoint for server-to-server status polling, webhook verification fallback, or order fulfillment confirmation.

POST /payment/check Check transaction status

Request Body application/json

Parameter Type Required Description
api_key string required Your secret SnapPay API key.
business_id integer required Your registered business identifier.
transaction_id string required Your application's unique transaction reference.

Response Statuses

The status field in the response reflects the Stripe payment intent lifecycle:

HTTP Code Status Value Description
200 success Payment captured and confirmed. Returns full transaction details including card info, customer, amount, and date.
200 requires_action 3D Secure authentication is required. Redirect the customer to the authentication URL to proceed.
202 processing Payment is being processed. Poll again in 2–5 seconds. This state is typically brief.
400 failed Payment failed or was cancelled. The card was declined or the payment method is invalid. Prompt the customer to retry.
500 unknown Unexpected internal state. Log the transaction_ref and contact SnapPay support.

Success Response (200 — succeeded)

{
  "success": true,
  "status": "success",
  "message": "Payment confirmed",
  "transaction_ref": "TXN-2026-XXXXXXXXXXXX",
  "transaction_id": "your-transaction-id",
  "amount": 4900,
  "currency": "USD",
  "card_last4": "4242",
  "card_brand": "visa",
  "customer_name": "John Doe",
  "customer_email": "john@example.com",
  "product_name": "Pro Plan - Monthly",
  "date_time": "22/03/2026 14:35:20",
  "http_code": 200
}
{
  "success": true,
  "status": "requires_action",
  "requires_action": true,
  "transaction_ref": "TXN-2026-XXXXXXXXXXXX",
  "transaction_id": "your-transaction-id",
  "message": "Authentification 3D Secure requise",
  "http_code": 200
}
{
  "success": true,
  "status": "processing",
  "transaction_ref": "TXN-2026-XXXXXXXXXXXX",
  "http_code": 202
}
{
  "success": true,
  "status": "failed",
  "transaction_ref": "TXN-2026-XXXXXXXXXXXX",
  "http_code": 400
}
{
  "success": false,
  "status": "error",
  "message": "Validation failed",
  "errors": {
    "api_key": ["The api key field is required."],
    "business_id": ["The business id field is required."]
  }
}
JSON Response

Checkout SDK

The FinalPay SDK lets your customers pay via a secure hosted checkout popup — no PCI complexity on your side. Include the script, initialize with your credentials, and open the checkout in one call.

Step 1 — Include the SDK

<!-- Add before closing </body> tag -->
<script src="https://api.usesnappay.com/finalpay-sdk.js"></script>
HTML

Step 2 — Initialize

Call FinalPay.initialize() once with your credentials before opening the checkout.

FinalPay.initialize(config)
Parameter Type Required Description
apikey string required Your SnapPay API key. Use spay_test_ prefix for sandbox, spay_live_ for production.
business_id integer required Your registered business identifier.
mode string optional TEST or LIVE. Defaults to LIVE.

Step 3 — Open Checkout

Call FinalPay.openCheckout() to open the payment popup. Typically triggered on a button click.

FinalPay.openCheckout(options)
Parameter Type Required Description
transaction_id string required Your unique transaction reference. Must be unique per payment attempt. Example: 'TRX-' + Date.now()
amount integer required Amount in the smallest currency unit (cents). Example: 8200 = $82.00 USD.
currency string required 3-letter ISO currency code. Supported: USD, EUR, XOF, XAF, GHS, NGN, KES, ZAR.
description string optional Product or service description shown in the checkout popup.
customer_name string optional Customer's full name. Pre-fills the checkout form.
customer_phone string optional Customer's phone number in E.164 format. Example: +15551234567

Step 4 — Handle Callbacks

Use FinalPay.onPaymentResponse() to handle the payment result and FinalPay.onError() for errors.

data.status Origin Meaning & Action
ACCEPTED succeeded Payment fully captured. data.reference, data.amount, data.currency, data.card_brand and data.card_last4 are available. → Fulfill the order.
REFUSED failed / canceled Payment was declined or cancelled. data.message contains the reason. → Prompt customer to retry with another card.
PENDING processing Payment is being processed asynchronously. → Poll /payment/check with the transaction_id until status resolves.
3DS requires_action 3D Secure authentication required. The SDK handles the redirect automatically. No action needed — wait for the final ACCEPTED or REFUSED callback.

Complete Example

<!DOCTYPE html>
<html>
<head>
  <script src="https://api.usesnappay.com/finalpay-sdk.js"></script>
</head>
<body>
  <button onclick="checkout()">Pay $82.00</button>
 
  <script>
  function checkout() {
 
    // 1. Initialize with your credentials
    FinalPay.initialize({
      apikey:      'spay_live_xxxxxxxxxxxxxxxxxxxx',
      business_id: 451278963,
      mode:        'LIVE' // or 'TEST' for sandbox
    });
 
    // 2. Open the checkout popup
    FinalPay.openCheckout({
      transaction_id: 'TRX-' + Date.now() + '-' + Math.floor(Math.random() * 100000),
      amount:         8200,       // 82.00 USD in cents
      currency:       'USD',
      description:   'Premium Training',
      customer_name: 'Jean Dupont',
      customer_phone:'+15551234567',
    });
 
    // 3. Handle payment result
    FinalPay.onPaymentResponse(function(data) {
 
      if (data.status === 'ACCEPTED') {
        console.log('✓ Payment confirmed');
        console.log('Ref:',    data.reference);
        console.log('Amount:', data.amount, data.currency);
 
        if (data.payment_method === 'card') {
          console.log('Card:', data.card_brand, '****' + data.card_last4);
        }
 
        // Redirect to success page
        window.location.href = '/payment/success?ref=' + data.reference;
      }
 
      if (data.status === 'REFUSED') {
        console.error('✗ Payment refused:', data.message);
        alert('Payment failed: ' + data.message);
      }
 
      if (data.status === 'PENDING') {
        // Poll /payment/check until resolved
        pollPaymentStatus(data.transaction_id);
      }
    });
 
    // 4. Handle errors (network, config, etc.)
    FinalPay.onError(function(err) {
      console.error('SDK Error:', err.message);
    });
  }
 
  // Poll helper for PENDING status
  async function pollPaymentStatus(txId, retries = 5) {
    for (let i = 0; i < retries; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const res  = await fetch('/api/payment/check', { /* your backend proxy */ });
      const data = await res.json();
      if (data.status !== 'processing') {
        console.log('Final status:', data.status);
        break;
      }
    }
  }
  </script>
</body>
</html>
import { useEffect } from 'react';
 
// Load SDK once in index.html or via useEffect
export default function CheckoutButton({ product, customer }) {
 
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://api.usesnappay.com/finalpay-sdk.js';
    document.body.appendChild(script);
    return () => document.body.removeChild(script);
  }, []);
 
  const handleCheckout = () => {
    window.FinalPay.initialize({
      apikey:      process.env.REACT_APP_SNAPPAY_KEY,
      business_id: process.env.REACT_APP_BUSINESS_ID,
      mode: 'LIVE',
    });
 
    window.FinalPay.openCheckout({
      transaction_id: `TRX-${Date.now()}`,
      amount:         product.priceInCents,
      currency:       product.currency,
      description:   product.name,
      customer_name: customer.name,
    });
 
    window.FinalPay.onPaymentResponse((data) => {
      if (data.status === 'ACCEPTED') {
        window.location.href = `/success?ref=${data.reference}`;
      } else if (data.status === 'REFUSED') {
        alert(data.message);
      }
    });
 
    window.FinalPay.onError((err) => console.error(err));
  };
 
  return (
    <button onClick={handleCheckout}>
      Pay {product.currency} {(product.priceInCents / 100).toFixed(2)}
    </button>
  );
}
<!-- Vue 3 Composition API -->
<template>
  <button @click="checkout">Pay {{ (amount / 100).toFixed(2) }} {{ currency }}</button>
</template>
 
<script setup>
import { onMounted } from 'vue'
 
const props = defineProps(['amount', 'currency', 'description'])
 
onMounted(() => {
  const s = document.createElement('script')
  s.src = 'https://api.usesnappay.com/finalpay-sdk.js'
  document.body.appendChild(s)
})
 
function checkout() {
  window.FinalPay.initialize({
    apikey:      import.meta.env.VITE_SNAPPAY_KEY,
    business_id: import.meta.env.VITE_BUSINESS_ID,
    mode: 'LIVE',
  })
 
  window.FinalPay.openCheckout({
    transaction_id: `TRX-${Date.now()}`,
    amount:         props.amount,
    currency:       props.currency,
    description:   props.description,
  })
 
  window.FinalPay.onPaymentResponse((data) => {
    if (data.status === 'ACCEPTED')
      window.location.href = `/success?ref=${data.reference}`
    else if (data.status === 'REFUSED')
      alert(data.message)
  })
 
  window.FinalPay.onError((e) => console.error(e))
}
</script>
HTML + JS

Callback Data Fields

The data object received in onPaymentResponse() contains the following fields:

Field Type Description
status string ACCEPTED, REFUSED, or PENDING
reference string SnapPay's internal transaction reference (e.g. CARD-69C07...). Use for support inquiries.
transaction_id string Your own transaction reference passed in openCheckout().
amount number Amount in the smallest currency unit. Available on ACCEPTED.
currency string 3-letter ISO currency code. Available on ACCEPTED.
payment_method string Payment method used. Currently card.
card_brand string Card brand e.g. Visa, Mastercard. Available when payment_method === 'card'.
card_last4 string Last 4 digits of the card used. Available when payment_method === 'card'.
message string Human-readable reason for failure. Available on REFUSED.
Never trust the client-side callback alone for order fulfillment. Always confirm the payment server-side using POST /payment/check or via webhooks before delivering goods or services.

Code Examples

Complete, production-ready code examples for the POST /payment/check endpoint in 10+ languages.

<?php
// Install: composer require guzzlehttp/guzzle

use GuzzleHttp\Client;

$client = new Client();

try {
    $response = $client->post('https://api.usesnappay.com/payment/check', [
        'headers' => [
            'Content-Type'  => 'application/json',
            'Accept'        => 'application/json',
        ],
        'json' => [
            'api_key'        => getenv('SNAPPAY_API_KEY'),
            'business_id'    => 42,
            'transaction_id' => 'order_ABC123',
        ],
    ]);

    $data = json_decode($response->getBody(), true);

    switch ($data['status']) {
        case 'success':
            echo " Payment confirmed: " . $data['transaction_ref'];
            break;
        case 'requires_action':
            echo "3DS required for: " . $data['transaction_id'];
            break;
        case 'processing':
            echo " Still processing, retry in 3s";
            break;
        case 'failed':
            echo "Payment failed";
            break;
    }

} catch (\Exception $e) {
    error_log('SnapPay error: ' . $e->getMessage());
}
// Install: npm install axios

const axios = require('axios');

async function checkPayment(transactionId) {
  try {
    const { data } = await axios.post(
      'https://api.usesnappay.com/payment/check',
      {
        api_key:        process.env.SNAPPAY_API_KEY,
        business_id:    42,
        transaction_id: transactionId,
      },
      { headers: { 'Content-Type': 'application/json' } }
    );

    switch (data.status) {
      case 'success':
        console.log(` Paid by ${data.customer_name} — ${data.amount} ${data.currency}`);
        break;
      case 'requires_action':
        console.log('3D Secure required');
        break;
      case 'processing':
        console.log(' Processing...');
        setTimeout(() => checkPayment(transactionId), 3000);
        break;
      case 'failed':
        console.log('Payment failed');
        break;
    }
  } catch (err) {
    console.error('SnapPay error:', err.response?.data ?? err.message);
  }
}

checkPayment('order_ABC123');
# Install: pip install requests

import os
import requests

BASE_URL = "https://api.usesnappay.com"

def check_payment(transaction_id: str) -> dict:
    payload = {
        "api_key":        os.getenv("SNAPPAY_API_KEY"),
        "business_id":    42,
        "transaction_id": transaction_id,
    }
    response = requests.post(
        f"{BASE_URL}/payment/check",
        json=payload,
        timeout=10,
    )
    response.raise_for_status()
    data = response.json()

    status = data.get("status")
    if status == "success":
        print(f" Confirmed — {data['customer_name']} paid {data['amount']} {data['currency']}")
    elif status == "requires_action":
        print("3DS authentication required")
    elif status == "processing":
        print(" Still processing...")
    elif status == "failed":
        print("Payment failed or cancelled")
    else:
        print(f"? Unknown status: {status}")

    return data

if __name__ == "__main__":
    check_payment("order_ABC123")
// Browser — Vanilla JavaScript (fetch API)
// Never expose your api_key in frontend code!

async function checkPayment(transactionId) {
  const res = await fetch('https://api.usesnappay.com/payment/check', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key:        'spay_test_your_key',
      business_id:    42,
      transaction_id: transactionId,
    }),
  });

  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = await res.json();

  const handlers = {
    success:         () => console.log('Payment confirmed ', data),
    requires_action: () => console.log('3DS required ', data),
    processing:      () => setTimeout(() => checkPayment(transactionId), 3000),
    failed:          () => console.log('Payment failed '),
  };

  (handlers[data.status] ?? (() => console.warn('Unknown status', data)))();
}

checkPayment('order_ABC123');
// Java 11+ — HttpClient (no external dependency)

import java.net.URI;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;

public class SnapPayClient {
    private static final String BASE_URL = "https://api.usesnappay.com";
    private static final HttpClient client = HttpClient.newHttpClient();

    public static String checkPayment(String transactionId) throws Exception {
        String body = String.format("""
            {"api_key":"%s","business_id":42,"transaction_id":"%s"}
            """, System.getenv("SNAPPAY_API_KEY"), transactionId);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + "/payment/check"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        System.out.println(response.body());
        return response.body();
    }

    public static void main(String[] args) throws Exception {
        checkPayment("order_ABC123");
    }
}
// Kotlin — OkHttp  |  build.gradle: implementation("com.squareup.okhttp3:okhttp:4.12.0")

import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject

val client = OkHttpClient()
val JSON_MT = "application/json; charset=utf-8".toMediaType()

fun checkPayment(transactionId: String): String {
    val payload = JSONObject().apply {
        put("api_key",        System.getenv("SNAPPAY_API_KEY"))
        put("business_id",    42)
        put("transaction_id", transactionId)
    }.toString()

    val request = Request.Builder()
        .url("https://api.usesnappay.com/payment/check")
        .post(payload.toRequestBody(JSON_MT))
        .addHeader("Accept", "application/json")
        .build()

    client.newCall(request).execute().use { response ->
        val body = response.body?.string() ?: ""
        when (JSONObject(body).getString("status")) {
            "success"         -> println(" Payment confirmed")
            "requires_action" -> println("3DS required")
            "processing"      -> println(" Processing...")
            "failed"          -> println("Failed")
        }
        return body
    }
}

fun main() = checkPayment("order_ABC123")
// C# / .NET 6+ — System.Net.Http (built-in)

using System.Net.Http.Json;
using System.Text.Json;

var http = new HttpClient { BaseAddress = new Uri("https://api.usesnappay.com") };

async Task CheckPaymentAsync(string transactionId) {
    var payload = new {
        api_key        = Environment.GetEnvironmentVariable("SNAPPAY_API_KEY"),
        business_id    = 42,
        transaction_id = transactionId,
    };

    var response = await http.PostAsJsonAsync("/payment/check", payload);
    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadFromJsonAsync<JsonDocument>();
    var status = json!.RootElement.GetProperty("status").GetString();

    switch (status) {
        case "success":         Console.WriteLine(" Payment confirmed"); break;
        case "requires_action": Console.WriteLine("3DS required"); break;
        case "processing":      Console.WriteLine(" Processing..."); break;
        case "failed":          Console.WriteLine("Payment failed"); break;
    }
}

await CheckPaymentAsync("order_ABC123");
// Go — standard library only

package main

import (
    "bytes"; "encoding/json"; "fmt"; "net/http"; "os"
)

type CheckRequest  struct { APIKey string `json:"api_key"`; BusinessID int `json:"business_id"`; TransactionID string `json:"transaction_id"` }
type CheckResponse struct { Status string `json:"status"`; TxRef string `json:"transaction_ref"`; Amount int `json:"amount"` }

func checkPayment(txID string) (*CheckResponse, error) {
    b, _ := json.Marshal(CheckRequest{os.Getenv("SNAPPAY_API_KEY"), 42, txID})
    resp, err := http.Post("https://api.usesnappay.com/payment/check", "application/json", bytes.NewBuffer(b))
    if err != nil { return nil, err }
    defer resp.Body.Close()
    var r CheckResponse
    json.NewDecoder(resp.Body).Decode(&r)
    return &r, nil
}

func main() {
    r, _ := checkPayment("order_ABC123")
    fmt.Printf("Status: %s | Ref: %s\n", r.Status, r.TxRef)
}
# Ruby — net/http (standard library)

require 'net/http'
require 'json'
require 'uri'

def check_payment(transaction_id)
  uri  = URI.parse('https://api.usesnappay.com/payment/check')
  http = Net::HTTP.new(uri.host, uri.port)
  req  = Net::HTTP::Post.new(uri.request_uri)
  req['Content-Type'] = 'application/json'
  req.body = { api_key: ENV['SNAPPAY_API_KEY'], business_id: 42, transaction_id: transaction_id }.to_json

  data = JSON.parse(http.request(req).body)

  case data['status']
  when 'success'         then puts " Paid by #{data['customer_name']}"
  when 'requires_action' then puts '3DS required'
  when 'processing'      then puts ' Processing...'
  when 'failed'          then puts 'Payment failed'
  end
end

check_payment('order_ABC123')
// Swift 5.5+ — URLSession + async/await

import Foundation

struct PayCheckRequest: Codable {
    let apiKey: String; let businessId: Int; let transactionId: String
    enum CodingKeys: String, CodingKey {
        case apiKey = "api_key"; case businessId = "business_id"; case transactionId = "transaction_id"
    }
}

func checkPayment(transactionId: String) async throws {
    var req = URLRequest(url: URL(string: "https://api.usesnappay.com/payment/check")!)
    req.httpMethod = "POST"
    req.setValue("application/json", forHTTPHeaderField: "Content-Type")
    req.httpBody = try JSONEncoder().encode(PayCheckRequest(
        apiKey:        ProcessInfo.processInfo.environment["SNAPPAY_API_KEY"] ?? "",
        businessId:    42,
        transactionId: transactionId
    ))

    let (data, _) = try await URLSession.shared.data(for: req)
    let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]

    switch json?["status"] as? String {
    case "success":         print(" Payment confirmed")
    case "requires_action": print("3DS required")
    case "processing":      print(" Processing...")
    case "failed":          print("Payment failed")
    default:                print("? Unknown status")
    }
}
// Dart / Flutter — http package  |  pubspec.yaml: http: ^1.2.0

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<void> checkPayment(String transactionId) async {
  final response = await http.post(
    Uri.parse('https://api.usesnappay.com/payment/check'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'api_key':        const String.fromEnvironment('SNAPPAY_API_KEY'),
      'business_id':    42,
      'transaction_id': transactionId,
    }),
  );

  if (response.statusCode >= 400) throw Exception('HTTP ${response.statusCode}');

  switch (jsonDecode(response.body)['status']) {
    case 'success':         print(' Confirmed'); break;
    case 'requires_action': print('3DS required'); break;
    case 'processing':      print(' Processing...'); break;
    case 'failed':          print('Payment failed'); break;
  }
}

void main() => checkPayment('order_ABC123');
# cURL — Quick testing

curl -X POST "https://api.usesnappay.com/payment/check" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "api_key":        "spay_test_your_api_key_here",
    "business_id":    42,
    "transaction_id": "order_ABC123"
  }'

# Pretty-print with jq
curl -s -X POST "https://api.usesnappay.com/payment/check" \
  -H "Content-Type: application/json" \
  -d '{"api_key":"spay_test_your_api_key_here","business_id":42,"transaction_id":"order_ABC123"}' \
  | jq .
PHP

Error Codes

All error responses follow a consistent JSON structure. The success field will be false, and message will describe the issue.

HTTP Code Error Type Description & Resolution
422 Validation Failed One or more required fields are missing or invalid. Check the errors object in the response for field-level details. Ensure api_key (string), business_id (integer), and transaction_id (string) are all provided.
401 Invalid API Key The api_key does not match any registered merchant. Verify you are using the correct key from your dashboard and the right environment (test vs. live).
403 Invalid Business ID The business_id is not associated with the provided api_key. Each API key belongs to a merchant — only business IDs owned by that merchant are valid.
404 Transaction Not Found No transaction matching the given transaction_id was found for this business. Confirm the transaction was created via SnapPay and that you are querying the correct environment (sandbox vs. production).
400 Payment Failed / Cancelled The payment intent was cancelled or the card was declined. Status will be "failed". Prompt the user to try a different payment method and re-initiate the checkout flow.
500 Unknown / Internal State The payment intent has an unexpected internal status. Log the transaction_ref from the response and contact SnapPay support at support@snappay.io with the reference.
503 Service Unavailable SnapPay's servers are temporarily unavailable. Implement exponential backoff retry logic (start at 1s, double each attempt, max 5 retries).
429 Rate Limit Exceeded You have exceeded the allowed request rate. See the X-RateLimit-Reset header for when your quota resets. The default limit is 300 requests/minute per API key.

Webhooks

Instead of polling /payment/check, configure your url_notification in your business settings to receive real-time POST notifications automatically on every payment status change.

Webhooks are signed with HMAC-SHA256 using your api_key as the secret. Always verify the X-SnapPay-Signature header before processing any webhook.

Events

Event Trigger Description
payment.success status = success Payment fully captured. Safe to fulfill the order.
payment.failed status = failed Payment declined or cancelled by the customer.
payment.requires_action status = requires_action 3D Secure challenge required. Redirect the customer.
payout.paid payout.status = paid Payout successfully sent to your bank account.
payout.failed payout.status = failed Payout failed. Contact support with the transaction ref.
payout.canceled payout.status = canceled Payout was cancelled before processing.

Request Headers

HeaderDescription
X-SnapPay-Signature sha256=HMAC_SHA256(payload, api_key) — verify this before processing.
X-SnapPay-Event The event type e.g. payment.success
X-SnapPay-Delivery Unique UUID per delivery — use for idempotency.

Payload Example

{
    "event":           "payment.success",
    "transaction_ref":  "CARD-69C0722161B3B-1774219809",
    "transaction_id":   "TRX-1774219787662-73987",
    "status":           "success",
    "amount":           82.00,
    "currency":         "USD",
    "customer_name":    "Jean Dupont",
    "customer_email":   "jean@example.com",
    "card_brand":       "visa",
    "card_last4":       "4242",
    "product_name":     "Premium Training",
    "business_id":      "451278963",
    "timestamp":        "2026-03-22T22:50:13+00:00"
}
<?php
    // Récupérer le payload brut et la signature
    $payload   = file_get_contents('php://input');
    $signature = $_SERVER['HTTP_X_SNAPPAY_SIGNATURE'] ?? '';
    $apiKey    = 'your_api_key'; // votre clé API SnapPay

    // Calculer la signature attendue
    $expected = 'sha256=' . hash_hmac('sha256', $payload, $apiKey);

    // Comparer de manière sécurisée (timing-safe)
    if (!hash_equals($expected, $signature)) {
        http_response_code(401);
        exit('Invalid signature');
    }

    // Signature valide — traiter le webhook
    $data = json_decode($payload, true);

    switch ($data['event']) {
        case 'payment.success':
            // Valider la commande
            break;
        case 'payment.failed':
            // Notifier le client
            break;
        case 'payment.requires_action':
            // Rediriger vers 3DS
            break;
    }

    http_response_code(200);
    echo json_encode(['received' => true]);
const crypto = require('crypto');

    function verifyWebhook(rawBody, signature, apiKey) {
    const expected = 'sha256=' + crypto
        .createHmac('sha256', apiKey)
        .update(rawBody)
        .digest('hex');

    // Timing-safe comparison
    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(signature)
    );
    }

    // Express.js example
    app.post('/webhook/snappay', express.raw({type: 'application/json'}), (req, res) => {
    const signature = req.headers['x-snappay-signature'];
    const apiKey    = 'your_api_key';

    if (!verifyWebhook(req.body, signature, apiKey)) {
        return res.status(401).json({error: 'Invalid signature'});
    }

    const data = JSON.parse(req.body);
    console.log(`Event: ${data.event} — Ref: ${data.transaction_ref}`);

    res.status(200).json({received: true});
    });
import hmac
import hashlib
import json

def verify_webhook(raw_body: bytes, signature: str, api_key: str) -> bool:
    expected = 'sha256=' + hmac.new(
        api_key.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    # Timing-safe comparison
    return hmac.compare_digest(expected, signature)

# Flask example
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook/snappay', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-SnapPay-Signature', '')
    api_key   = 'your_api_key'

    if not verify_webhook(request.data, signature, api_key):
        return jsonify({'error': 'Invalid signature'}), 401

    data = request.get_json()
    print(f"Event: {data['event']} — Ref: {data['transaction_ref']}")

    return jsonify({'received': True}), 200
Webhook

Retry Policy

Attempt Delay Total elapsed
1st attempt Immediate 0s
2nd attempt +60s ~1 min
3rd attempt +5 min ~6 min
Permanently failed After 3 attempts, status is marked failed in webhook logs. Contact support with your transaction_ref.
Your endpoint must return HTTP 200 within 10 seconds. Always use the X-SnapPay-Delivery UUID to ensure idempotent processing — the same event may be delivered more than once.

Rate Limiting

The SnapPay API enforces rate limits to protect platform stability. Rate limit information is returned in the response headers:

X-RateLimit-Limit Maximum requests per minute for your API key (default: 300).
X-RateLimit-Remaining Number of requests remaining in the current window.
X-RateLimit-Reset Unix timestamp (UTC) when your quota resets.

Changelog

v1.0 March 2026 Initial public release — Payment Check endpoint, Auth, Response statuses, Error codes Latest

Test Cards

Use the following card numbers in the sandbox environment (any future expiry date, any CVC). No real charges are made. All test transactions are isolated from production.

These cards only work with your spay_test_ API key. Never use real card numbers in sandbox.
Brand Card Number CVC Expiry
Visa4242 4242 4242 4242Any 3 digitsAny future date
Visa (debit)4000 0566 5566 5556Any 3 digitsAny future date
Mastercard5555 5555 5555 4444Any 3 digitsAny future date
Mastercard (series 2)2223 0031 2200 3222Any 3 digitsAny future date
Mastercard (debit)5200 8282 8282 8210Any 3 digitsAny future date
Mastercard (prepaid)5105 1051 0510 5100Any 3 digitsAny future date
Amex3782 822463 10005Any 4 digitsAny future date
Amex3714 496353 98431Any 4 digitsAny future date
Discover6011 1111 1111 1117Any 3 digitsAny future date
Discover6011 0009 9013 9424Any 3 digitsAny future date
Discover (debit)6011 9811 1111 1113Any 3 digitsAny future date
Diners Club3056 9300 0902 0004Any 3 digitsAny future date
Diners Club (14-digit)3622 720627 1667Any 3 digitsAny future date
JCB3566 0020 2036 0505Any 3 digitsAny future date
UnionPay6200 0000 0000 0005Any 3 digitsAny future date
UnionPay (debit)6200 0000 0000 0047Any 3 digitsAny future date
UnionPay (19-digit)6205 5000 0000 0000 004Any 3 digitsAny future date

Click any card number to copy it.

These cards trigger 3D Secure authentication flows. Use them to test your requires_action handling.

Brand Card Number Behavior
Visa4000 0027 6000 3184Always requires 3DS authentication
Visa4000 0025 0000 31553DS required, authentication succeeds
Mastercard5200 8282 8282 82103DS optional, card enrolled
Visa4000 0082 6000 31783DS required, authentication fails

Use these to simulate declined payments and test your error handling flows.

Brand Card Number Decline Reason
Visa4000 0000 0000 0002generic_decline
Visa4000 0000 0000 9995insufficient_funds
Visa4000 0000 0000 9987lost_card
Visa4000 0000 0000 9979stolen_card
Visa4000 0000 0000 0069expired_card
Visa4000 0000 0000 0127incorrect_cvc
Visa4000 0000 0000 0119processing_error
Visa4242 4242 4242 4241incorrect_number

Co-branded cards are issued under two networks simultaneously. The following simulate successful payments.

Brand / Co-brand Card Number CVC
Cartes Bancaires / Visa4000 0025 0000 1001Any 3 digits
Cartes Bancaires / Mastercard5555 5525 0000 1001Any 3 digits
eftpos Australia / Visa4000 0503 6000 0001Any 3 digits
eftpos Australia / Mastercard5555 0503 6000 0080Any 3 digits
BCcard / DinaCard6555 9000 0060 4105Any 3 digits

Use these PaymentMethod IDs directly in API calls instead of raw card numbers.

Brand PaymentMethod ID
Visapm_card_visa
Visa (debit)pm_card_visa_debit
Mastercardpm_card_mastercard
Mastercard (debit)pm_card_mastercard_debit
Mastercard (prepaid)pm_card_mastercard_prepaid
Amexpm_card_amex
Discoverpm_card_discover
Diners Clubpm_card_diners
JCBpm_card_jcb
UnionPaypm_card_unionpay

Legacy token IDs for older integrations. Most integrations should use PaymentMethods instead.

Brand Token
Visatok_visa
Visa (debit)tok_visa_debit
Mastercardtok_mastercard
Mastercard (debit)tok_mastercard_debit
Mastercard (prepaid)tok_mastercard_prepaid
Amextok_amex
Discovertok_discover
Diners Clubtok_diners
JCBtok_jcb
UnionPaytok_unionpay
Click any row to copy
Copied!