Security best practices
Read this before going live. Most integration security issues trace back to a few common mistakes, and this page covers all of them.
API key storage
- Store in environment variables, never in source control. A key in a
.envfile that gets committed to git is compromised the moment it hits a remote. Use your hosting platform’s secret manager (AWS Secrets Manager, GCP Secret Manager, Vault, Doppler, Railway env, etc.). - One key per integration role. Create a read-only key for reporting jobs and a write key for the invoice server. If one leaks, you revoke it without touching the other.
- Add an IP allowlist on every production key. Your invoice server has a static IP, so pin the key to it. A stolen key then cannot be used from an attacker’s machine.
- Rotate keys quarterly (cabinet → Settings → API Keys → Rotate). Rotate immediately on any staff departure or suspected exposure.
- Repeated bad-secret attempts trigger a temporary per-key lockout. The lockout is per-key, so an attacker probing one key doesn’t affect your others. Integrations should back off on
429 API_KEY_LOCKED.
Webhook security
- Always verify
Swap-Pay-Signature. Without verification, any server that knows your webhook URL can send fakeinvoice.paidevents. See the verification guide for Node, Python, PHP, Go, and curl samples. - Reject events older than 5 minutes to prevent replay attacks. All samples in the verification guide enforce this.
- Dedupe by
event_id. We resend the same UUID on every retry. Store the ID and skip processing on duplicates. - Optionally allowlist the SwapSS Pay outbound IPs at your firewall. See the webhooks overview for nginx / Cloudflare / Caddy examples.
- Use HTTPS only. We refuse deliveries to
http://, private ranges, loopback, and link-local addresses. - Respond 2xx within 10 seconds. Slow responses trigger retries. If your handler needs longer, return 200 immediately and process asynchronously.
Account security (2FA)
- Enable TOTP 2FA on every cabinet account. Cabinet sessions last 30 days. A leaked session cookie without 2FA is a full takeover.
- Download and store recovery codes offline when you enable 2FA. Without them, a lost authenticator device locks you out of your own account.
- If you employ staff with cabinet access, enforce 2FA per-seat. Create a shared ops email rather than sharing personal credentials.
Hosted checkout
- The
payment_urlincludes a tamper-evident token, so altered URLs return 404. Send it to your customer exactly as returned by the API. - Do not iframe the checkout page. We set
X-Frame-Options: DENYandframe-ancestors 'none'. success_urlandfail_urlmust be HTTPS. Don’t embed secrets in the redirect target, because the customer can see it.- Always verify payment status server-side via webhook or
GET /api/v1/merchant/invoices/:idbefore fulfilling an order. The redirect tosuccess_urlis a UX hint only, not an authoritative confirmation.
Money handling in your code
- Store amounts as integers in minor units. Never use floats. A float like
5.00001is not5000001microunits: floating-point rounding causes silent discrepancies over many transactions. - Wrong-amount deposits are held for review. Do not try to "round to nearest" in your code; let the API handle it.
Audit logs
The cabinet shows an audit log of every API key creation, rotation, revocation, and every payout / refund action. Review it periodically. If you see activity you don't recognise, rotate your keys immediately and contact support.
Quick security checklist
text
[ ] API key stored in secret manager, not in code or .env in git
[ ] Separate keys per integration role (read vs write)
[ ] IP allowlist set on every production key
[ ] TOTP 2FA enabled on your cabinet account
[ ] Webhook signature verified on every incoming request
[ ] Event deduplication implemented (store event_id)
[ ] Webhook endpoint rejects events older than 5 min
[ ] success_url/fail_url are HTTPS, no secrets in URL
[ ] Payment status confirmed via API/webhook before order fulfil
[ ] Amounts stored as integers (minor units), never floats
[ ] Key rotation calendar reminder set (quarterly)