Security & custody
A concrete description of how funds, keys, and data are handled. No marketing.
Custody model
Spawnpay uses custodial wallets on Base L2 (chainId 8453) and an internal ledger. Deposit addresses must come from the ledger service. This page does not establish signer isolation, multisig custody, or an automated sweep policy.
Your access credential is the spk_ bearer key. Keys are verified with constant-time HMAC/hash comparison and never logged in plaintext. Rotating a key via POST /api/key/rotate invalidates prior tokens immediately.
Ledger correctness
All internal sub-cent settlements go through a Redis-locked ledger. Every credit/debit on a user account acquires lock:ledger:<userId> before mutating credit_balance, funded_balance, or related counters. The lock pattern uses Redis SETNX with an expiry — no two writes for the same user race, even across replicas.
// from packages/backend/src/ledger.js
const lock = await acquireLock(`lock:ledger:${userId}`);
try {
// mutate balance + write tx row + write dedup marker
} finally {
releaseLock(lock);
}
Idempotency keys are mandatory on send / proxy / scrape / screenshot. Same key within the same minute bucket returns the prior result instead of double-charging. Per-tool refunds on upstream failure roll the charge back to the caller's balance in the same locked critical section.
Auth surface
- User auth:
spk_bearer key inAuthorizationheader. Constant-time comparison. Rate limited per IP + per key. - Admin auth: separate
x-admin-secretheader on admin endpoints. Cross-origin denied with403 Cross-origin admin call blocked. - Webhook verification: Svix signature on inbound provider webhooks where applicable. Timing-safe verify.
- HTTPS:
helmetsecurity headers (HSTS, CSP, X-Content-Type-Options) on the gateway. Strict-Transport-Security max-age 2 years.
Panic mode
The backend ships with a PANIC_MODE env flag. When set, all paid endpoints return 503 immediately, no balance mutations, no upstream calls. Designed for the case where a vulnerability is suspected and the only safe action is to halt money movement. Reads (balance, deposits) stay available so users can see state.
Data retention
| What | Where | Retention |
|---|---|---|
| spk_ key (hash) | SQLite (VPS) | Indefinite while account active |
| Transaction rows (sends, charges, deposits) | SQLite (VPS) + Base L2 | Indefinite |
| Proxy request body | NOT stored | Discarded after upstream forward |
| Proxy response body | NOT stored | Discarded after caller return |
| Caller IP for rate limiting | Redis | 24h rolling window |
| Audit log of admin actions | SQLite (VPS) | Indefinite |
Known limits — what we do NOT claim
Disclosure
Security disclosures: security@spawnpay.ai. Please include reproduction steps and affected endpoint; verified findings receive prompt remediation and public credit upon request.