Emprise
Security

We never touch the money. We prove it moved.

Emprise is non-custodial. Funds move directly from your customer's wallet to your wallet, on-chain. Our job is narrower and harder to get wrong: make sure the right address was paid the right amount, then tell you about it safely.

Non-custodial by design

No hot wallet. No balance. No withdrawal surface. Nothing to drain, nothing to freeze, nothing to lose. The customer signs a transaction to your address directly; we read the chain to confirm it landed.

No funds held
Customer wallet pays merchant wallet directly on-chain.
No cardholder data
We never touch PANs or cards, so PCI scope does not apply today.
One tx, one session
A unique index prevents a single transaction from paying twice.

Four invariants we never break

A payment app's security reduces to these. Each one has a concrete, testable control behind it.

01

Recipient is write-once

The payout address is set server-side when you create a session and can never be changed by a public endpoint, a settle call, or any client input. Payments cannot be redirected.

02

Paid only when the chain says so

A session is marked paid only after on-chain verification: the right recipient, the right token, enough amount, enough confirmations. The customer's claim is never trusted.

03

Webhooks are signed

Every outbound webhook carries an HMAC-SHA256 signature with a timestamp. You verify the raw body, reject stale timestamps, and dedupe by event id.

04

Secrets stay secret

API keys are hashed at rest and never logged. Webhook secrets live encrypted in the database. Public responses return only what the checkout needs to render.

Threats, and how we stop them

Forged or double payment
Server-side on-chain verification, idempotent settle, and a unique index that makes one transaction settle exactly one session. A replayed tx is rejected.
Recipient substitution
The payout address is bound to the authenticated merchant at creation and locked. Address format is validated per chain family.
API key theft
Keys are high-entropy, stored only as a SHA-256 hash, compared in constant time, sent over TLS only, and never appear in logs or errors.
XSS via merchant config
Merchant-supplied text is rendered as escaped React text, never as HTML. Logo URLs must be https, accent colours are validated, and field lengths are bounded.
SQL injection
Every database access uses parameterised D1 queries. No value is ever interpolated into SQL.
SSRF via webhook URL
Webhook URLs must be https and are blocked from localhost and link-local ranges. Delivery follows no redirects, so a 30x cannot exfiltrate the signed body.
Abuse and DoS
Rate limiting on the public and merchant write paths, a 64 KiB body cap, bounded fields, and strict input validation.

Infrastructure and transport

TLS and HSTS everywhere
Cloudflare enforces HTTPS on the whole edge. Webhook delivery is https-only and rejects http targets.
Strict content security
A locked-down CSP on the SPA. Google Fonts and the analytics beacon are the only third-party origins.
Least-privilege responses
Public endpoints return only render config. Merchant data is scoped: a cross-merchant request gets a 404.
Safe by construction
Parameterised queries, zod validation, bounded bodies and fields, and errors that never leak internals.
Compliance

Honest about where we stand

PCI DSS · OUT OF SCOPE

No cardholder data, by design

We never store, process or transmit PANs or cards, so PCI DSS does not currently apply. De-scoping is itself a valid PCI strategy. Adding card or fiat rails would re-enter scope.

SOC 2 · IN PROGRESS

Type 2 is the target

Our controls map to the Trust Services Criteria. We will claim SOC 2 only after a CPA engagement and an observation window, not before. This page documents the controls, not a certification.

Found a security issue?

We welcome responsible disclosure. Report it to us and we will respond fast.

Report securely