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 .env file 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 fake invoice.paid events. 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_url includes 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: DENY and frame-ancestors 'none'.
  • success_url and fail_url must 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/:id before fulfilling an order. The redirect to success_url is 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.00001 is not 5000001 microunits: 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)

See also