On this page
Security
Learn how Paymos protects token balances with 2-of-3 MPC signing, HMAC authentication, signed webhooks, SSRF controls, and audit records.
Paymos is a custodial gateway — we hold the keys that move your funds on-chain. That puts a fair question in front of every merchant: what stops Paymos from taking my money, and what happens if something breaks?
This page answers both — first as a short list of promises, then in plain language with the reasoning behind each one. It describes the guarantees the platform gives you and what each one buys you, not an internal blueprint. Where a detail is part of the public API (how you sign a request, how you verify a webhook), we document it precisely, because you need it to integrate.
Why your funds are safe
No one — not even Paymos — can move your funds alone
- Threshold custody. The keys that sign withdrawals are split across multiple, physically separate signers. Authorizing a transfer requires a quorum of them to co-sign. No single machine and no single employee ever holds a complete key. Paymos as a company cannot unilaterally sign away your balance.
- Mandatory whitelist. Funds can only ever leave to destinations you approved in advance. Even a total compromise of your API credentials cannot invent a new payout address.
- Owner-controlled by default. The ability to withdraw, and the ability to edit the whitelist, are reserved for the account owner unless you explicitly delegate them — and every grant is logged.
- You withdraw on your schedule. Your balance is yours the moment a payment reaches finality. We do not lock funds. Withdraw hourly, daily, or never.
- Segregated balances. Each merchant's balance is tracked separately. We do not pool merchant funds, and we do not lend or rehypothecate them.
Your money won't silently disappear
- We credit only final payments — deep enough on-chain that they cannot be reorganized away.
- No clawbacks. Once we tell you a payment is confirmed, we never reverse it. If a confirmed payment is later dropped by a reorg, Paymos absorbs the loss — not you.
- Payouts can't double-send. Every withdrawal carries your own idempotency key.
- Layered freeze controls can halt a single asset automatically if on-chain reality drifts from the ledger.
- Liquidity is checked up front, so a withdrawal fails fast instead of getting stuck.
- Tamper-evident records: every privileged action is audited, and the database is backed up daily in encrypted form.
No clawbacks. Once we tell you a payment is confirmed, we never reverse it. If a confirmed payment is later dropped by a reorg, Paymos absorbs the loss — not you.
The rest of this page explains each of these in plain terms. If you've seen the words MPC, HMAC, or SSRF and wondered what they mean — read on.
Threat model
We design against:
- Funds at rest — keys that could move balances on-chain
- Funds in motion — withdrawal authorization and destination tampering
- API access — credential theft, request replay, scope escalation
- Webhook delivery — forged payloads, and use of our infrastructure as an SSRF pivot into yours
- Operational risk — accidental destructive actions, multi-server race conditions, audit gaps
Explicitly out of scope: your customer's wallet security (they hold their own keys), recipient-side risk after a withdrawal signs (on-chain transactions are final), third-party services you run alongside Paymos, and social engineering of your team.
MPC / threshold signing — why funds can't be moved by one party
MPC stands for Multi-Party Computation. Above it's called "threshold custody" — same thing.
The problem. Funds on a blockchain are controlled by a private key — a long secret string. Whoever has the key can move the money. Normally that's one string on one machine, which brings two dangers: if the key leaks, funds are gone; and if one person or server holds it, they can move funds alone — and compromising that one machine is enough.
What MPC does. The key is mathematically split into shares, and the shares live on separate, physically distributed signers. Signing a transfer requires several of them to cooperate — no single signer can sign on its own. The non-obvious part: the full key is never assembled anywhere — not even during signing. Each signer does part of the math with its own share, the parts combine into a finished signature, but the whole key never exists in any one place: not in memory, not on disk, not for a second.
Analogy. Say the safe's password is the word "BANANA." Bad idea: hand it to one guard. Smart idea: split it into encrypted fragments and give them to three people. When two of them "compute together" — without ever showing each other their fragments — the safe gets the right answer and opens. But "BANANA" was never typed, spoken, or written down. Even if an attacker watches one guard's screen forever, all they see is a meaningless fragment.
The classic shape of this is "2 of 3": three signers, any two required. Why that, and not something else:
- 1 of 1 — a single point of failure: breach one machine, lose everything.
- 3 of 3 — if one machine goes down, funds are frozen forever.
- 2 of 3 — the sweet spot: one machine can fail (the other two still sign), but one compromised machine is not enough.
What this buys you — the "we won't rug you" part: no single employee can move funds, one breached server does not equal stolen funds, and Paymos as a company can't move your balance by fiat — there is no place that holds a whole key, and no single button.
Every signing request is itself authenticated and bound to the exact transaction it authorizes; a replayed or altered request is rejected before any share is used. And every invoice receives its own unique deposit address — never reused across customers, so reconciliation is unambiguous. Sandbox payments and withdrawals never touch the production signing path, so you can test with zero risk to live funds.
HMAC — how the API knows a request is really from you
HMAC stands for Hash-based Message Authentication Code. Scary name, simple idea.
The problem. Your server sends us "create a $1,000 withdrawal." We have two questions: is this really you, or an impostor? And was the request tampered with in transit — the amount or address changed? If you just put a password inside the request, anyone who intercepts the traffic steals it and sends requests as you.
What HMAC does. You and we share a secret (your API secret). For each request you compute a "fingerprint" (a signature) — a hash of the request contents together with the secret. You send the request and the fingerprint, but not the secret. We recompute the same fingerprint with our copy of the secret. If they match, it's really you, and nothing was changed — alter a single character and the fingerprint won't line up.
Analogy. A wax seal on a letter. The secret is your unique stamp. Everyone can see the seal, but they can't forge it without the stamp. And if the letter is opened and rewritten, the seal no longer matches the contents.
The key point: the secret itself never travels over the network. So even recording all your traffic, an attacker can't extract the secret or craft a new request — they'd only hold signatures for requests you already sent. (SHA-256 is the specific "mill" that grinds any input into a fixed-length fingerprint that can't be reversed.)
The exact format (for your integration)
Every authenticated request carries two headers:
Authorization: HMAC-SHA256 {api_key_id}:{base64(signature)}
X-Request-Timestamp: {unix_seconds}
The signature is HMAC-SHA256 over a canonical string:
{timestamp}\n{METHOD}\n{path}\n{query}\n{sha256_hex_lower(body)}
An empty body contributes an empty string in that slot — it is not hashed; query parameters are signed exactly as they appear in the URL. Full details on the Authentication page.
Anti-replay protection
Could an attacker resend an old request they recorded — replay "create withdrawal" a hundred times? The timestamp is part of the signature, and we reject any request whose X-Request-Timestamp is more than ±5 minutes off server time (rejected with 401). A recorded request lives five minutes, then dies. Combined with idempotency (below), even within that window a replayed "create withdrawal" won't send funds twice.
Analogy. A ticket with a five-minute entry window — yesterday's won't work.
Constant-time comparison
When we compare two signatures, a naive comparison stops at the first wrong character — and the response time would reveal how many characters are already right, letting an attacker guess byte by byte. We compare in constant time: a "no" takes exactly the same time no matter how much you guessed.
Analogy. A combination lock that says "no" in the same time whether you got one digit right or five. You can't tell you're getting "warmer."
Key types and rotation
Two key types, each pinned to a fixed scope, enforced on the server for every call:
- Payment key — create and read invoices. Cannot read balances or create withdrawals.
- Payout key — create withdrawals; read whitelists, accounts, and balances. Cannot create invoices.
Secrets rotate without downtime: you generate a new secret while the previous one stays valid until previous_secret_expires_at (default 24 hours). Both are accepted during the grace window, then only the new one works.
Webhook security — the same HMAC, in reverse
HMAC above protects your requests to us. A webhook is us knocking on your door ("invoice paid"). Same trick, reversed: we sign every webhook with your endpoint secret, and you verify the signature before trusting it. Otherwise anyone who learns your webhook URL could send a fake "paid" event and trick you into shipping goods.
X-Webhook-Signature: t={unix_seconds},v1={hex_hmac}
The signed payload is {timestamp}.{request_body}. The scheme matches Stripe's, so libraries that verify Stripe webhooks work here with almost no changes; during secret rotation we emit both signatures in the same header. See Verifying Webhooks.
SSRF protection
SSRF stands for Server-Side Request Forgery. You set a webhook URL, and our servers make requests to it. A clever attacker points the URL not outward but inside our network — at a cloud-metadata address or an internal panel — so our server fetches it on their behalf, turning Paymos into a proxy for things only our network can see.
We prevent that: a webhook destination is validated to resolve to a public address — rejecting loopback, private, internal, and cloud-metadata ranges — and the connection is pinned to that validated address so there's no DNS-rebinding window. Redirects are refused. A destination that fails validation is dropped immediately, so probing can't burn delivery budget.
Analogy. A courier who only delivers to real external addresses, refuses to carry anything "to this building's own server room," checks the address before leaving, and ignores a note saying "actually, deliver it over there instead."
Delivery semantics
Webhook URLs must use HTTPS (enforced when you save the endpoint). Delivery is at-least-once — deduplicate by event ID on your side (the convention Stripe, GitHub, and PayPal use). Failed deliveries are retried with exponential backoff across 11 attempts spanning roughly 16 hours — the full schedule is on Delivery and retries — then marked failed and replayable from the dashboard. Each attempt has its own timeout.
Withdrawal protection — several locks in a row
Moving money out is where a custodial gateway lives or dies, so it has the most layers:
- Whitelist (mandatory). A withdrawal goes only to addresses you approved in advance. If the whitelist for a network family is empty, no withdrawal can be created at all — there's no "the first withdrawal sets the address" shortcut. Even with stolen keys, funds can only go to your own wallets, so stealing keys loses its payoff. Analogy: an account that can only wire to a pre-registered list of recipients — a thief with your password can't add a new payee.
- Owner only. Creating withdrawals and editing the whitelist belong to the account owner, not to a standard admin role. You must explicitly grant them to a non-owner, and that grant is auditable.
- Two-factor (2FA). A six-digit code from an authenticator app, changing every 30 seconds. Even someone who stole your password doesn't have your phone. Creating a withdrawal from the dashboard requires it.
- Freeze controls (circuit breakers). We can freeze everything at once, a single token on a single network, or a single merchant. A per-asset freeze can engage automatically if on-chain balances drift from the ledger beyond a safe margin.
- Up-front liquidity check. A withdrawal is checked against available funds (including fees) before it's accepted, so it fails immediately with a clear error instead of getting stuck.
- Idempotency. Your order identifier is the idempotency key — send it twice and you get back the same single withdrawal, never a second transfer. Analogy: a coat-check ticket; hand in the same ticket twice and you get the same one coat back.
- Race-safe creation. Concurrent withdrawal requests for the same account are serialized server-side, so two parallel calls can't slip past the same balance check.
Authorization & isolation
- Every request is checked server-side before it does anything: the environment must match (sandbox vs production), the caller type must be allowed, the credential's scope must cover the operation, and the resource must belong to the caller. These checks are the real boundary — UI controls are only hints.
- 404, not 403. If a resource doesn't exist, or exists but isn't yours, you get a
404. So an attacker can't tell "this ID is real but not yours" from "this ID doesn't exist." Analogy: a doorman who says "no such person" whether they don't exist or live there but you're not allowed up. - Your identity comes from your credential, never the request body. Putting someone else's identifier in a request does nothing — you can't act on behalf of another merchant by spoofing an ID.
- Granular permissions. Access is governed by fine-grained, role-based permissions (e.g. withdraw, view balance, manage webhooks), each grantable independently. The server-side check is the boundary; the dashboard mirrors it.
Rate limiting
Each merchant has its own rate limits. When you exceed one you receive 429 Too Many Requests with a Retry-After header, so a single misbehaving integration can only affect its own throughput, not the platform or other merchants.
On-chain finality — why your money won't disappear
On a blockchain a transaction "appears" in a block quickly, but the chain can still rewrite recent history (a "reorg") — and something that looked confirmed can vanish. Like wet ink: written, but it can still smudge.
Finality means waiting until the transaction is buried deep enough that it can't be reversed. The ink has dried. We wait for each network's appropriate finality before moving your balance. On chains where finality is a matter of confirmation depth, that depth scales with the amount — small tickets clear fast, large ones wait longer; chains with native instant finality settle as soon as the block is final. These thresholds are tuned to network conditions, not fixed.
The main promise: if a payment we already confirmed is later dropped by a reorg (very rare), Paymos absorbs the loss, not you. Once a confirmation webhook fires, you can act on it immediately. As a bonus, there are no chargebacks (unlike cards, which a buyer can reverse for months after the sale), and late payments are impossible by design — a payment confirmed after an invoice expired won't credit that expired invoice.
Data protection
In transit: all traffic is TLS 1.2+, HSTS is enabled in production, and un-encrypted requests are redirected to HTTPS.
At rest: the database is backed up daily in encrypted form with a 30-day rolling retention window; session cookies are HttpOnly, Secure, SameSite=Strict; sign-in links are single-use, expire quickly, and are encrypted at rest.
Anti-forgery: dashboard actions that change state carry an anti-forgery token, which together with SameSite=Strict cookies mitigates cross-site request forgery.
Audit log: every privileged action records who did it, what ran, the outcome, and when. Sensitive values — secrets, passwords, tokens, hashes, signatures — are redacted before the record is stored, enforced at write time.
Operational resilience
- Multi-server safety. Running across several servers never double-processes a payment, withdrawal, or webhook — concurrent work on the same item is coordinated to happen exactly once. Verified by automated tests, not assumed.
- Privacy-safe error reporting. Crash reports are scrubbed of personal data and request contents before they leave our systems.
- Self-healing infrastructure. Automated health checks pull an unhealthy instance out of rotation.
- Safe schema changes. Database migrations are applied once, in order, and are forward-only — we never run destructive rollbacks against production data.
Compliance & data handling
No KYC to start. You can register and begin testing without submitting identity, address, or beneficial-owner documents, and Paymos does not collect those documents from your customers. Paymos does not vet your business, check licences, screen merchant categories, or run sanctions screening on your customers; those obligations remain with the merchant. We may request KYC/KYB later if activity requires manual review, account limits are exceeded, or a competent authority requires it, as described in our AML/KYC policy.
Customer-side data. By design, Paymos collects no personal data from your customers at checkout — no email, phone, name, or card data. The flow is invoice → payment page → wallet pays → done. We see wallet addresses and amounts, not identities, so your GDPR/CCPA exposure through Paymos is effectively zero.
Merchant-side data. For your account we hold business name, contact email, country, and project metadata (webhook URLs, API key identifiers). A GDPR deletion request triggers a 30-day soft delete followed by permanent purge; the audit trail retains only a hash of the deleted record.
Data residency. Primary data is stored in the EU. Enterprise contracts can pin a specific region — contact us.
What we don't do
- We don't claim things we haven't built. This page describes the system as deployed. Roadmap items aren't listed until they ship.
- We don't custody longer than needed. Funds reach your balance the moment a payment confirms; withdrawal timing is your choice.
- We don't pool merchant balances. Each balance is segregated. One merchant's insolvency or dispute can't touch another's funds. Pooling a shared pot is exactly what sank exchanges like FTX.
- We don't promise recipient-side safety. Once a withdrawal signs, the transaction is on-chain and final. The whitelist prevents typos and key theft — it can't validate that a destination you approved will behave.
Reporting a vulnerability
Send disclosures to [email protected].
- Initial response: within 24 hours
- Triage: within 72 hours
- Scope: production
paymos.ioand*.paymos.io, the production REST API, and Paymos-published SDKs and CMS plugins - Out of scope: rate-limit-based denial of service, social engineering, third-party services (your CDN, hosting provider, etc.)
- Bug bounty: no public program currently; private disclosure of material findings is rewarded case-by-case, with acknowledgment on request.
Please don't test against other merchants' accounts, use destructive testing against production, or disclose publicly before we've had a chance to fix. We commit to acknowledge your report within 24 hours, keep you posted on the fix, credit you (with your permission) when it ships, and not pursue legal action against good-faith research within scope.