Security
A factual overview of how Qint protects accounts, payments and data — the controls that are in place today, not a list of intentions.
Last updated:
1. The model in one paragraph
Qint is the software layer: dashboard, checkout, invoices, API and webhooks. It never holds crypto assets and never holds private keys. Deposit addresses are issued by our execution partner, 24crypto, which takes custody of incoming funds, converts them and pays the merchant's bank account. A compromise of Qint cannot move funds; it could at most expose the data described in the Privacy policy, which is what the rest of this page is about.
The API runs on Fly.io in Frankfurt, the database is a Postgres instance at Neon in the EU (Frankfurt), and the web applications are served by Vercel. Secrets are injected from a secrets manager at deploy time and are not stored in the code.
2. Accounts and sign-in
- Passwords are hashed with Argon2id (memory-hard, per-user salt) and compared in constant time. We never store or log the password itself.
- E-mail verification is required before the first sign-in. Links are single-use and expire after 24 hours; password-reset links after one hour. Responses do not reveal whether an address has an account.
- Two-factor authentication (TOTP, RFC 6238) is optional for every user. The seed is stored encrypted, codes cannot be replayed, and ten one-time recovery codes are shown once and stored as hashes. Disabling 2FA requires the password again.
- Brute-force protection. Sign-in endpoints are limited to 10 requests per minute per IP address. Failed sign-ins are recorded in the audit log with the IP address.
3. Sessions
- Sign-in sets one cookie,
qint_session: HttpOnly, Secure, SameSite=Lax. The server stores only a SHA-256 hash of the session token. - Sessions slide for 30 days of activity and end at the latest 90 days after sign-in. Signing out revokes the session; changing the password revokes every other session; resetting the password revokes all sessions.
- Each session records the IP address and user-agent so that unusual activity can be reviewed.
4. Roles and access control
Every team member holds one role per merchant; the API enforces it on each request.
| Role | Can |
|---|---|
| Owner | Everything, including granting the Owner role and closing the account. |
| Admin | Manage the merchant, team, payments, payouts, API keys and webhooks. |
| Developer | Manage API keys and webhooks; create and view payments. |
| Finance | Request payouts and review underpayments; create and view payments. |
| Viewer | Read-only access. |
Team invitations are e-mail-bound, single-use and expire after seven days; only an Owner can grant the Owner role. Our own staff console has two roles, Admin and a read-only Moderator, and is separated from the merchant dashboard.
5. API keys
- Keys have the prefix
qk_live_and 32 random characters. They are shown exactly once. - Only a SHA-256 hash is stored, with the prefix, a label, the scopes (read, write), the creator and the last time the key was used.
- Revocation takes effect immediately and is recorded in the audit log. A key cannot be used by a merchant that is not cleared to transact.
- The public API is limited to 120 requests per minute per key.
6. Webhooks
- Every delivery is signed:
X-Qint-Signature: sha256=<HMAC-SHA256 of the raw body>, computed with the endpoint's secret, plus anX-Qint-Event-Idfor deduplication. - Signing secrets (
whsec_…) are shown once and stored encrypted with AES-256-GCM. - Endpoints must use HTTPS and resolve to public IP addresses; redirects are not followed, which prevents the delivery worker from being pointed at internal services.
- Failed deliveries are retried after 30 s, 2 min, 10 min, 30 min and 2 h (six attempts in total) with a 10-second timeout, then marked as dead-lettered and shown in the dashboard's delivery log.
7. Payment integrity
- A payment moves through a fixed state machine — Initiated → Pending → Confirmed → Settled, with Failed, Expired and Cancelled as terminal states — and the API rejects any transition that is not allowed. Underpaid and overpaid amounts are flagged, not silently accepted.
- Creating a payment through the API is idempotent: an idempotency key is unique per merchant, and a retried request returns the original payment instead of creating a second one. Requests to the execution partner carry their own idempotency keys.
- Background workers expire stale payments, reconcile state with the execution partner and keep payout status in sync.
8. Audit trail
Security-relevant events — sign-ins and failures, 2FA changes, password changes and resets, team changes, API key creation and revocation, exports, receipt e-mails — are written to an audit log with the actor, the IP address and the time. The application refuses to update or delete audit rows and terms-acceptance rows; they can only be appended.
9. Encryption and hardening
- In transit: TLS everywhere. The API sends
Strict-Transport-Security(one year, including subdomains). - At rest: the database is encrypted by the provider. On top of that the application encrypts 2FA seeds, webhook signing secrets, execution-partner credentials and verification invitation e-mails with AES-256-GCM, and stores passwords, session tokens, API keys, recovery codes, invitation and reset tokens only as hashes.
- Headers:
X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: no-referrer. A Content Security Policy is deployed in report-only mode while it is tuned. - Rate limits: 10 requests per minute per IP for sign-in, 120 per minute per IP for the checkout, 120 per minute per key for the API. Request bodies are capped at 1 MiB.
- Origins: the dashboard and checkout each have their own CORS allow-list; the session cookie is never sent cross-site.
- Client IP handling: forwarded IP headers are trusted only when they carry a shared proxy secret, so addresses in the audit log cannot be spoofed.
10. Monitoring
Application errors are reported to Sentry when enabled, configured to send no personal data by default and no performance traces. Webhook health, failed deliveries and reconciliation differences are visible in the staff console, and the dashboard notifies merchant owners and admins about payment reviews, reversals, verification status and suspensions by e-mail.
11. Custody and payouts
Funds are held by 24crypto, a Swiss financial intermediary affiliated with the self-regulatory organisation VQF. Qint stores the merchant's payout IBAN to set up the account; payout requests to the execution partner reference the merchant's account there, and the staff console shows the IBAN masked. Qint has no wallet software, no signing keys and no ability to move crypto.
12. Reporting a vulnerability
If you have found a security issue in Qint, please e-mail info@qint.ch. Describe what you found, how to reproduce it and which part of the service is affected. We acknowledge reports within two business days, keep you informed while we fix the issue and credit you if you wish. We do not run a bug bounty programme.
Please test only against your own account, do not access or modify other people's data, do not run denial-of-service tests and give us reasonable time to fix an issue before publishing it. The same information is published at /.well-known/security.txt.