SDK quickstarts

Official SDKs are coming Q4 2026. Until then, these copy-paste snippets cover the three operations you need for a complete integration: create an invoice, check its status, and verify webhook deliveries.

Replace sspay_sec_test_demo_YOUR_KEY with your actual API key (found in the cabinet under API Keys). Never hardcode keys in source code; use environment variables.

1. Create an invoice

POST to /api/v1/merchant/invoices. Returns a payment_url to send your customer and a unique id to track the invoice in your database.

Node / TypeScript
typescript
import crypto from 'node:crypto';

const API_BASE = 'https://swapss.lol/api/v1/merchant';
const API_KEY  = process.env.SWAPSS_PAY_API_KEY!; // sspay_sec_test_demo_xxxxxxx

interface CreateInvoiceParams {
  amount:       string; // decimal string, e.g. "5.00"
  currency:     string; // e.g. "USDT"
  chain:        string; // e.g. "tron"
  orderId:      string; // your unique order reference
  description?: string;
  successUrl?:  string;
  failUrl?:     string;
  webhookUrl?:  string;
}

async function createInvoice(params: CreateInvoiceParams) {
  const idempotencyKey = crypto.randomUUID();

  const res = await fetch(`${API_BASE}/invoices`, {
    method: 'POST',
    headers: {
      'Authorization':   `Bearer ${API_KEY}`,
      'Idempotency-Key': idempotencyKey,
      'Content-Type':    'application/json',
    },
    body: JSON.stringify({
      amount:      params.amount,
      currency:    params.currency,
      chain:       params.chain,
      order_id:    params.orderId,
      description: params.description,
      success_url: params.successUrl,
      fail_url:    params.failUrl,
      webhook_url: params.webhookUrl,
    }),
  });

  if (!res.ok) {
    const err = await res.json();
    throw new Error(`SwapSS Pay error: ${err.error?.code} — ${err.error?.message}`);
  }

  return res.json();
}

// Example
const invoice = await createInvoice({
  amount:     '5.00',
  currency:   'USDT',
  chain:      'tron',
  orderId:    'ORD-1042',
  successUrl: 'https://example.com/thanks',
  failUrl:    'https://example.com/oops',
  webhookUrl: 'https://example.com/swap-pay-webhook',
});

console.log(invoice.payment_url); // send this to your customer
Python
python
import os
import uuid
import requests

API_BASE = "https://swapss.lol/api/v1/merchant"
API_KEY  = os.environ["SWAPSS_PAY_API_KEY"]  # sspay_sec_test_demo_xxxxxxx


def create_invoice(
    amount: str,
    currency: str,
    chain: str,
    order_id: str,
    description: str = "",
    success_url: str = "",
    fail_url: str = "",
    webhook_url: str = "",
) -> dict:
    idempotency_key = str(uuid.uuid4())

    resp = requests.post(
        f"{API_BASE}/invoices",
        headers={
            "Authorization":   f"Bearer {API_KEY}",
            "Idempotency-Key": idempotency_key,
            "Content-Type":    "application/json",
        },
        json={
            "amount":      amount,
            "currency":    currency,
            "chain":       chain,
            "order_id":    order_id,
            "description": description,
            "success_url": success_url,
            "fail_url":    fail_url,
            "webhook_url": webhook_url,
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()


# Example
invoice = create_invoice(
    amount="5.00",
    currency="USDT",
    chain="tron",
    order_id="ORD-1042",
    success_url="https://example.com/thanks",
    fail_url="https://example.com/oops",
    webhook_url="https://example.com/swap-pay-webhook",
)
print(invoice["payment_url"])  # send this to your customer
PHP
php
<?php
use GuzzleHttp\Client;

$apiBase = 'https://swapss.lol/api/v1/merchant';
$apiKey  = getenv('SWAPSS_PAY_API_KEY');  // sspay_sec_test_demo_xxxxxxx

$http = new Client(['timeout' => 30]);

function createInvoice(
    Client $http,
    string $apiBase,
    string $apiKey,
    string $amount,
    string $currency,
    string $chain,
    string $orderId,
    string $description = '',
    string $successUrl  = '',
    string $failUrl     = '',
    string $webhookUrl  = '',
): array {
    $idempotencyKey = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
        mt_rand(0, 0xffff), mt_rand(0, 0xffff),
        mt_rand(0, 0xffff),
        mt_rand(0, 0x0fff) | 0x4000,
        mt_rand(0, 0x3fff) | 0x8000,
        mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
    );

    $response = $http->post("{$apiBase}/invoices", [
        'headers' => [
            'Authorization'   => "Bearer {$apiKey}",
            'Idempotency-Key' => $idempotencyKey,
            'Content-Type'    => 'application/json',
        ],
        'json' => [
            'amount'      => $amount,
            'currency'    => $currency,
            'chain'       => $chain,
            'order_id'    => $orderId,
            'description' => $description,
            'success_url' => $successUrl,
            'fail_url'    => $failUrl,
            'webhook_url' => $webhookUrl,
        ],
    ]);

    return json_decode($response->getBody(), true);
}

// Example
$invoice = createInvoice(
    $http, $apiBase, $apiKey,
    amount: '5.00', currency: 'USDT', chain: 'tron',
    orderId: 'ORD-1042',
    successUrl: 'https://example.com/thanks',
    failUrl:    'https://example.com/oops',
    webhookUrl: 'https://example.com/swap-pay-webhook',
);
echo $invoice['payment_url']; // send this to your customer

2. Check invoice status

Polling works for simple integrations, but webhooks are better for production: you don't need a tight poll loop and you handle every state transition automatically.

Node / TypeScript
typescript
async function getInvoice(invoiceId: string) {
  const res = await fetch(`${API_BASE}/invoices/${invoiceId}`, {
    headers: { 'Authorization': `Bearer ${API_KEY}` },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

// Poll until paid or terminal state
async function waitForPayment(invoiceId: string, maxWaitMs = 60_000) {
  const terminalStates = new Set(['paid', 'expired', 'cancelled', 'failed']);
  const start = Date.now();

  while (Date.now() - start < maxWaitMs) {
    const inv = await getInvoice(invoiceId);
    if (terminalStates.has(inv.status)) return inv;
    await new Promise(r => setTimeout(r, 5_000)); // poll every 5s
  }
  throw new Error('timeout waiting for payment');
}
Python
python
import time


def get_invoice(invoice_id: str) -> dict:
    resp = requests.get(
        f"{API_BASE}/invoices/{invoice_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()


def wait_for_payment(invoice_id: str, max_wait: int = 60) -> dict:
    terminal = {"paid", "expired", "cancelled", "failed"}
    deadline = time.monotonic() + max_wait

    while time.monotonic() < deadline:
        inv = get_invoice(invoice_id)
        if inv["status"] in terminal:
            return inv
        time.sleep(5)  # poll every 5s

    raise TimeoutError("payment not received in time")
PHP
php
function getInvoice(Client $http, string $apiBase, string $apiKey, string $invoiceId): array {
    $response = $http->get("{$apiBase}/invoices/{$invoiceId}", [
        'headers' => ['Authorization' => "Bearer {$apiKey}"],
    ]);
    return json_decode($response->getBody(), true);
}

function waitForPayment(Client $http, string $apiBase, string $apiKey, string $invoiceId, int $maxWait = 60): array {
    $terminal = ['paid', 'expired', 'cancelled', 'failed'];
    $deadline = time() + $maxWait;

    while (time() < $deadline) {
        $inv = getInvoice($http, $apiBase, $apiKey, $invoiceId);
        if (in_array($inv['status'], $terminal, true)) return $inv;
        sleep(5);
    }

    throw new RuntimeException('payment not received in time');
}

3. Verify a webhook

Every delivery carries a Swap-Pay-Signature header. Verify it before processing. Full explanation at Webhook verification.

Node / TypeScript
typescript
import crypto from 'node:crypto';

const WEBHOOK_SECRET = process.env.SWAP_PAY_WEBHOOK_SECRET!;

function verifySignature(header: string, rawBody: Buffer | string, toleranceSec = 300): boolean {
  const parts: Record<string, string> = {};
  for (const kv of header.split(',')) {
    const eq = kv.trim().indexOf('=');
    if (eq > 0) parts[kv.slice(0, eq).trim()] = kv.slice(eq + 1).trim();
  }
  const t = Number.parseInt(parts['t'] ?? '', 10);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;

  const body = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');
  const mac  = crypto.createHmac('sha256', WEBHOOK_SECRET).update(`${t}.${body}`).digest('hex');
  const a    = Buffer.from(mac, 'hex');
  const b    = Buffer.from(parts['v1'] ?? '', 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express handler
app.post('/swap-pay-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifySignature(req.headers['swap-pay-signature'] as string ?? '', req.body)) {
    return res.status(401).json({ error: 'invalid signature' });
  }
  const event = JSON.parse(req.body.toString());
  // Dedupe by event.event_id, then handle event.type
  res.json({ ok: true });
});
Python
python
import hmac, hashlib, time, os, json
from flask import Flask, request, abort

WEBHOOK_SECRET = os.environ["SWAP_PAY_WEBHOOK_SECRET"].encode()

def verify_signature(header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    parts = {}
    for kv in header.split(","):
        kv = kv.strip()
        eq = kv.find("=")
        if eq > 0:
            parts[kv[:eq].strip()] = kv[eq+1:].strip()
    try:
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(WEBHOOK_SECRET, f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

app = Flask(__name__)

@app.post("/swap-pay-webhook")
def webhook():
    if not verify_signature(request.headers.get("Swap-Pay-Signature", ""), request.get_data()):
        abort(401)
    event = json.loads(request.get_data())
    # Dedupe by event["event_id"], then handle event["type"]
    return {"ok": True}
PHP
php
<?php
function verifySignature(string $header, string $rawBody, string $secret, int $tolerance = 300): bool {
    $parts = [];
    foreach (explode(',', $header) as $kv) {
        $kv = trim($kv);
        $eq = strpos($kv, '=');
        if ($eq !== false) $parts[trim(substr($kv, 0, $eq))] = trim(substr($kv, $eq + 1));
    }
    $t = (int)($parts['t'] ?? 0);
    if (!$t || abs(time() - $t) > $tolerance) return false;
    $expected = hash_hmac('sha256', "{$t}.{$rawBody}", $secret);
    return hash_equals($expected, $parts['v1'] ?? '');
}

$rawBody = file_get_contents('php://input');
$header  = $_SERVER['HTTP_SWAP_PAY_SIGNATURE'] ?? '';
$secret  = getenv('SWAP_PAY_WEBHOOK_SECRET');

if (!verifySignature($header, $rawBody, $secret)) {
    http_response_code(401);
    echo json_encode(['error' => 'invalid signature']);
    exit;
}

$event = json_decode($rawBody, true);
// Dedupe by $event['event_id'], then handle $event['type']
echo json_encode(['ok' => true]);

Environment variables used

VariableValue
SWAPSS_PAY_API_KEYYour secret API key (sspay_sec_test_demo_xxxxxxx). Found in the cabinet under API Keys.
SWAP_PAY_WEBHOOK_SECRETYour webhook signing secret. Shown once when you create the webhook subscription in the cabinet.

What's next


Want to contribute an SDK? Email sdk@swapss.lol with a link to your repository. Community SDKs are listed here once they pass a basic review.