Quickstart

From zero to your first live invoice in about 5 minutes. You’ll need a merchant account (free) and a wallet to send a test payment from.

Step 1: Register a merchant account

Go to /auth/register and sign up. No KYC at signup, no credit card.

Step 2: Apply for merchant access

Open /cabinet/merchant-apply and fill in your business details. An operator reviews applications and typically approves them within minutes during business hours.

Step 3: Wait for operator approval

Status updates in the cabinet. Once approved, the Merchant section becomes active and the API endpoints below start working for your account.

Step 4: Generate an API key

In the cabinet, go to Settings → API Keys and create a key. Choose the scopes you need:

ScopeAllows
invoices.writeCreate and cancel invoices (includes read)
invoices.readRead invoice details and list
payouts.writeRequest withdrawals (includes read)
payouts.readRead payout details
refunds.writeIssue refunds on paid invoices
balance.readRead your merchant balance and ledger

Copy the secret key (sspay_sec_test_demo_xxxxxxx) — it’s shown once. Store it in a secret manager, not in code.

Step 5: Create your first invoice

Replace sspay_sec_test_demo_YOUR_KEY with your key. uuidgen (macOS / Linux) generates a fresh idempotency key.

bash
curl -X POST https://swapss.lol/api/v1/merchant/invoices \
  -H "Authorization: Bearer sspay_sec_test_demo_YOUR_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "5.00",
    "currency": "USDT",
    "chain": "tron",
    "description": "My first invoice",
    "order_id": "TEST-001",
    "success_url": "https://example.com/thanks",
    "fail_url":    "https://example.com/oops",
    "webhook_url": "https://example.com/swap-pay-webhook"
  }'

You should get back:

json
{
  "id": "INV-0123456789",
  "status": "awaiting_deposit",
  "pay_in": {
    "asset": "USDT",
    "chain": "tron",
    "address": "TH...example",
    "amount_decimal": "5.00",
    "amount_raw": "5000000"
  },
  "expires_at": "2026-05-18T12:34:56Z",
  "payment_url": "https://swapss.lol/pay/INV-...",
  "created_at": "2026-05-18T11:34:56Z",
  "service_fee_bps": 80
}

Step 6: Embed the payment link in your app

payment_url is the hosted checkout URL. Send it to your customer however makes sense — email, redirect, QR code, button. The page shows the deposit address, exact amount, and a live countdown.

The exact amount_decimal matters: the customer must send that amount exactly, down to the last digit. Instruct them to copy it precisely, or direct them to the hosted checkout, which shows the exact amount with a copy button.

html
<!-- Simplest integration: redirect -->
<a href="https://swapss.lol/pay/INV-0123456789.7c4f...">Pay with crypto</a>

<!-- Or open in a new tab -->
<a href="{payment_url}" target="_blank" rel="noopener">Pay with crypto</a>

Step 7: Receive the webhook

Once the deposit is confirmed on-chain, SwapSS Pay sends a signed POST request to your webhook_url. The event type for a successful payment is invoice.paid.

json
{
  "event_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "type": "invoice.paid",
  "created_at": "2026-05-18T11:40:12Z",
  "merchant_id": "MCH-AB12CDEF",
  "data": {
    "invoice_id": "INV-0123456789",
    "status": "paid",
    "credited": true,
    "amount_raw": "5000073"
  }
}

Verify the Swap-Pay-Signature header and return 200 within 10 seconds. See step 8 for the code.

Step 8: Verify the webhook signature

This is the most important security step. Without it, any server can send you fake payment confirmations.

javascript
// Node 18+ — no dependencies
import crypto from 'node:crypto';

const WEBHOOK_SECRET = process.env.SWAP_PAY_WEBHOOK_SECRET;

function verifySignature(header, rawBody, toleranceSec = 300) {
  const parts = Object.fromEntries(
    header.split(',').map(kv => {
      const eq = kv.trim().indexOf('=');
      return [kv.slice(0, eq).trim(), kv.slice(eq + 1).trim()];
    })
  );
  const t = Number.parseInt(parts.t, 10);
  if (!Number.isFinite(t)) return false;

  // Reject events outside the 5-minute replay window.
  if (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');
  // Constant-time compare prevents timing-side-channel attacks.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Example Express endpoint
app.post('/swap-pay-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['swap-pay-signature'] ?? '';
  if (!verifySignature(sig, req.body)) {
    return res.status(401).json({ error: 'invalid signature' });
  }
  const event = JSON.parse(req.body.toString());
  // Dedupe: store event.event_id and skip if already processed.
  console.log('Event received:', event.type, event.event_id);
  res.json({ ok: true });
});

Python, PHP, and Go samples at Webhook verification.


Optional: check your balance and withdraw

bash
# Check balance
curl https://swapss.lol/api/v1/merchant/balance \
  -H "Authorization: Bearer sspay_sec_test_demo_YOUR_KEY"

# Withdraw
curl -X POST https://swapss.lol/api/v1/merchant/payouts \
  -H "Authorization: Bearer sspay_sec_test_demo_YOUR_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "USDT",
    "chain": "tron",
    "amount": "4.90",
    "destination": "YOUR_TRON_ADDRESS"
  }'

What's next