Trust

Security: how Payzilla is built

This page describes how Payzilla is designed and what has been built and tested so far. It is not a certification and it is not an audit. We are a devnet preview with no real money, and we say what is still to come.

Internally reviewed. No external audit yet. Devnet and test tokens only.

What we do not claimReport a problem

In one screen

The true picture, in six lines.

AreaIn one lineStatus
TokensPayment tokens go wallet to wallet. Payzilla holds none.Built and tested
SigningThe signer signs only payments that match strict checks.Built and tested
KeysThe network-fee key is held in AWS KMS on private staging.Built and tested (staging)
RecordsAn append-only, balanced ledger.Built and tested
ReviewInternal reviews only. No external audit and no penetration test.Not yet
ScopeDevnet and test tokens. No real money, no real card provider.Devnet only

What is in place today

Payment tokens go wallet to wallet

The buyer’s wallet signs the transfer and the tokens go to the seller’s registered wallet. No part of the API accepts a buyer’s or a seller’s private key. Payzilla’s signer holds only the network-fee key, and that account does not move the payment tokens.

The signer signs only checked payments

Before anything is signed, Payzilla checks the payment strictly: the exact layout of the transaction, a plain token transfer from the buyer, the approved token and its decimals, the exact amount, the seller’s registered destination, a memo equal to the payment id, limits on compute and fees, and that the fee account is not moving tokens. It simulates the transaction first, and signs one payment once. There is no general-purpose signing endpoint.

Keys

On devnet, the network-fee key is held in a managed key service (AWS KMS). In our private staging environment the API runs with that key and no development key, and its payments are signed that way. A local development key exists for local development only. The signer asks for a short-lived, single-use approval tied to one payment.

Row-level security separates each merchant’s data

Every merchant’s data is protected by row-level security in the database, and a request without a merchant identity sees nothing. Separate database roles are used for the API, the workers and the signer. We have tested this against a real PostgreSQL database on our own machines, and a 73-check role-isolation test has also passed on the managed staging database. Staging has only one merchant, so cross-merchant isolation there is checked with an unknown merchant that must see nothing.

An append-only, balanced ledger

Ledger entries are not changed or deleted: the database blocks it and entries are only added. Each set of entries must balance when it is committed, and a reversal must mirror the original.

Duplicate and replay protection

Database constraints stop the same transaction, the same buyer message or the same payload from settling twice. The signed transaction is stored before it is sent, and a retry resends the same bytes. A payment is marked failed only after the network shows it did not land.

Watching the fee account

If payment tokens ever appear in the network-fee account, signing freezes until the freeze is reviewed and lifted with a recorded approval. This monitor is built and tested with simulated balances. A separate low-balance alarm on the fee account also runs in staging.

Signed webhooks

Webhooks are signed with a timestamped signature, and deliveries go only to https addresses at public IPs. Redirects are not followed.

Start-up checks and logs

In the staging profile, the API refuses to start if it would use the development key, an embedded database, fewer than two independent connections to the network, or anything other than devnet. Logs are structured, and they redact secrets, web addresses and card-number-like digits. The API checks its own health, and shuts down gracefully so in-flight work finishes. These checks are tested locally and now run in the private staging environment.

Secrets

Merchant keys are stored only as hashes and shown once. The dashboard prototype is read-only, runs only on the local machine, and does not show the merchant key.

Card data

The card flow is designed so that card numbers do not reach our servers: the provider’s hosted fields collect them, and our API rejects anything that looks like a card number. It is tested against a mock provider. No real card provider is connected, and we claim no card-industry certification.

Card refunds and disputes (mock provider)

In the card flow, a refund is treated as an obligation: it is retried until the provider confirms it, and it is escalated if it keeps failing. An open dispute holds the payment instead of letting it slip through. A captured amount that does not match the payment, or a settlement line that contradicts our records, opens a reconciliation break and raises an alert. All of this is tested against a mock provider only.

The interactive demo

The demo page has its own guards: a human check (Cloudflare Turnstile), one run at a time, limits per address and per day, a time limit for each run, and a switch that turns it off. Its agent has a mandate: a maximum price per call and per run, and a single allow-listed recipient. It runs on devnet with a test token that has no value. In the demo, a tampered price or recipient is refused before anything is signed.

Devnet only, by design

Real-network use is refused in several places: in the connector configuration, in the seller middleware, and in the signer, which checks the network’s genesis hash.

How it has been checked

  • Several rounds of internal adversarial review: of the design, of the x402 build, of the demo showcase and of the card flow. They found real issues, and the fixes come with new tests. The findings of the latest card-flow review have been fixed and tested.
  • Mutation testing: we deliberately break the code in small ways and check that the tests notice. Most injected faults are caught, and the ones that are not are written down.
  • More than 1,000 automated tests pass against a real PostgreSQL database run on our own machines (last counted October 2026). The full suite has not been run against the managed cloud database; the staging environment has been exercised with the role-isolation test and with end-to-end payments.
  • The first end-to-end payments ran on the private staging environment: three test payments through Cloudflare to the API on AWS, signed by the KMS key, all finalized on devnet.
  • All of this is internal. There has been no external audit and no penetration test.

What we are hardening next

  • A separate network-fee key and account for each merchant, instead of one shared key.
  • A key policy, least-privilege access and break-glass procedures for the managed key, plus alerts.
  • Asymmetric approvals for signing, instead of a shared secret.
  • Per-address rate limits, and a public hosted environment.
  • The last reconciliation checks. Most checks that compare our records with the chain already run on staging.
  • Webhook secrets encrypted at rest, with rotation.
  • Hardening the staging environment: deploying the new minimal container image (it is built, and an internal image scan has been run), more independent network data providers, and a review of the key policy and access, before it opens to trusted testers.
  • An external review of the money path before any real money.

What we do not claim

  • No audit, no penetration test and no certifications.
  • No card-industry (PCI) compliance.
  • No production environment, no real money and no real network.
  • No separate key per merchant yet.

Report a problem

If you find a security problem, please write to [email protected]. Our security.txt has the same contact. For ordinary bugs and anything else, use the same address. We will read what you send.

Related: how a payment works, the developer reference and the roadmap.

Report a security problem