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:
| Scope | Allows |
|---|---|
invoices.write | Create and cancel invoices (includes read) |
invoices.read | Read invoice details and list |
payouts.write | Request withdrawals (includes read) |
payouts.read | Read payout details |
refunds.write | Issue refunds on paid invoices |
balance.read | Read 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.
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:
{
"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.
<!-- 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.
{
"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.
// 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
# 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
- Full API reference: every endpoint, parameter, response schema
- Idempotency guide: how to safely retry failed requests
- Webhooks overview: event catalog, IP allowlist, retry policy
- Webhook verification samples: Node, Python, PHP, Go, curl
- Error catalog: machine-readable error codes
- Testing guide: real-network testing strategy
- SDK snippets: Node / Python / PHP full snippets