On this page
Key takeaways
- A gateway that can move value has a political architecture even when the code is simple.
- Keys, RPC providers, and the database are not implementation details — they are the product boundary.
- The boundary sentence must survive both counsel and the on-call engineer.
- No hidden hosted signer and no custody mode: the implementation must follow the sentence.
A gateway that can move value has a political architecture even when the code is simple. Who holds the keys decides who is a vendor and who is an operator. This note explains how we drew that line in StablePay, and why we drew it so plainly.
Software or service?
Keys, RPC, and the database are not implementation details. They are the product. If the studio holds them, you bought a service. If you hold them, you bought software. The distinction has commercial, legal, and operational consequences, and it should be discoverable from the architecture, not the marketing.
| Component | Service model | StablePay (software model) |
|---|---|---|
| Signing keys | Held by the provider | Held in your environment |
| RPC providers | Provider's choice | The ones you operate |
| Database | Provider's ledger | PostgreSQL you run |
| Secrets | Provider-managed | Your secret store |
| Path of funds | Through the provider | Not through the studio |
The boundary sentence
We wrote one sentence and built to it: the customer operates the environment, the software records what it was asked to do, and the studio is not in the path of funds.
That sentence has to survive two very different conversations: one with counsel, who asks whether the studio is a custodian, and one with the on-call engineer, who asks where the key actually is at 2am. If the answer changes between those rooms, the architecture is lying.
Where the studio is
Outside the path of funds. It ships software and documentation. It holds no keys, runs no signer, and cannot move value on your behalf.
What the implementation rules out
- No hidden hosted signer. There is no fallback path where a studio-run service signs on your behalf.
- No "we can hold this for you" mode. Convenience features that require custody are not built.
- No dashboard that implies visibility we do not have. We cannot see a production wallet we do not run, and the interface does not pretend otherwise.
- No phone-home for funds. Operation does not depend on the studio being reachable.
Hidden signer
- A fallback where the vendor signs for you
- A dashboard implying visibility you do not have
- Operation depends on the vendor being reachable
A boundary you can draw
- Keys and signing process in your environment
- The gateway records intent; authority lives with you
- Works if the studio disappears tomorrow
Who is allowed to sign?
Once the boundary is drawn, the interesting question moves inward: inside your environment, who can trigger a signature, and how do you prove it later?
Separate intent from authority
The gateway records payment intents. Authority to move funds belongs to whoever operates your signing process. Keeping those separate means an application bug cannot, on its own, become a transfer.
Make approvals explicit
For flows like contractor settlement, the sequence at Helios Grid was: a field-close event created a payable, a supervisor approval authorised it, and only then did a payment intent exist. Each step had a named human or system, and each left a record.
Leave a trail
An audit trail is not a log dump. It is the ability to answer, months later: who approved this, under what policy, and what did the system do next?
A one-page threat model
Not a substitute for a security review — a way to keep the conversation concrete. For each row, name the control and the person who owns it.
| Threat | What it looks like | Where the control lives |
|---|---|---|
| Application bug creates a transfer | A retry loop or bad handler issues payments | Separation of intent from signing authority |
| Leaked signing key | Key in a repository or environment file | Secret store, restricted access, rotation |
| Forged webhook | Attacker marks an order as paid | Signature verification on the raw body |
| Compromised RPC provider | False or delayed chain data | Provider choice you control, confirmation policy |
| Insider approval abuse | One person authorises and executes | Named approvers and an audit trail |
| Unpatched deployment | Known issue never applied | Patch cadence and upgrade notes |
Operating the boundary
Owning the boundary also means owning its hygiene. Our handover guidance is deliberately dull:
- Keep signing keys in a secret store, not in environment files committed to a repository.
- Restrict who can reach the signing process, and log every use.
- Rotate on a schedule and after any personnel change that warrants it.
- Test recovery: can you rebuild the gateway from backups without the studio?
- Apply patch releases; a fixed vulnerability you have not deployed is still a vulnerability.
What we do not claim
StablePay is not a custodian, not a bank, and does not provide legal or compliance services. Whether a given operating model meets your regulatory obligations is a question for your counsel.
Questions to ask any gateway vendor
- Where do production signing keys live, and who can use them?
- Can the vendor move funds on my behalf, even in an emergency?
- If the vendor disappeared tomorrow, what stops working?
- Which parts of the payment record can I query directly?
- How would I prove, to an auditor, who approved a specific payment?
Boundary review before go-live
- Signing keys live in a secret store, not an environment file in a repo.
- Access to the signing process is restricted and every use is logged.
- Recovery has been tested: the gateway can be rebuilt from backups.
- Approvers are named, and no one person can authorise and execute.
- Patch releases have an owner and a cadence.
The key lifecycle
Keys are not a thing you have; they are a thing you manage across a life. NIST's key management guidance [1] describes the lifecycle in phases, and the useful takeaway for a payments team is that every phase needs an owner and a procedure, not just the moment of creation.
| Phase | Question to answer | Typical control |
|---|---|---|
| Generate | Where and how is the key created? | Inside the secret store or an HSM-backed service, never on a laptop |
| Distribute | How does it reach the signing process? | Pulled at runtime by an authenticated workload, not baked into an image |
| Use | Who or what can request a signature? | A narrow API, allow-listed callers, and logged requests |
| Rotate | How often, and what triggers an early rotation? | Calendar schedule plus personnel and incident triggers |
| Revoke | How quickly can a key stop being trusted? | A documented procedure that has been drilled |
| Destroy | How do we know old material is gone? | Deletion from every store and backup policy, recorded |
Where keys can live: a comparison
There is no single right place for a key, only trade-offs between convenience, isolation, and cost. The important discipline is choosing deliberately and being able to explain the choice.
| Option | Isolation | Operational cost | Good fit |
|---|---|---|---|
| Environment variable or file | Low — readable by anything that can read the process environment | Lowest | Local development only |
| Secret manager (e.g. Vault, cloud secret store) [3][4] | Medium — access controlled and audited, key still enters process memory | Low to medium | Most application secrets and signing keys at moderate value |
| Cloud KMS or HSM-backed signing [4] | High — key material never leaves the service, callers request signatures | Medium | Higher-value keys where exfiltration must be impossible |
| Multi-party or offline signing | Highest — no single party can sign alone | High | Treasury-scale funds where approval is the point |
Whichever tier you choose, the signing interface should be narrow. A service that only signs transactions matching a policy — allowed destinations, amount limits, and required approvals — is much safer than one that signs whatever it is handed. The OWASP guidance on secrets management [2] and cryptographic storage [5] is a solid baseline for the surrounding controls.
Separating intent from authority in practice
The gateway records what your application wants to happen. The signing process decides whether that is allowed. Keeping those apart means a bug in the application cannot, by itself, move value. A policy layer between them is where business rules become enforceable.
type Payout = { id: string; to: string; amount: bigint; approvals: string[] };
const policy = {
maxSingle: 50_000n * 10n ** 6n, // per-payout ceiling (token minor units)
requiredApprovals: (amount: bigint) => (amount > 10_000n * 10n ** 6n ? 2 : 1),
allowedDestinations: new Set<string>(loadVerifiedAddresses()),
};
export function authorise(p: Payout): { ok: true } | { ok: false; reason: string } {
if (p.amount > policy.maxSingle) return { ok: false, reason: "exceeds single-payment limit" };
if (!policy.allowedDestinations.has(p.to)) return { ok: false, reason: "destination not verified" };
const needed = policy.requiredApprovals(p.amount);
if (new Set(p.approvals).size < needed) return { ok: false, reason: `needs ${needed} distinct approvals` };
return { ok: true };
}Separation of duties
No single person should be able to both authorise and execute a payment. The concept is old and well defined [6]; the implementation is a table of who may do what, enforced in code and reviewed on a schedule.
What to log, and what never to
| Log this | Never log this |
|---|---|
| Who requested a signature, from where, and when | Private keys, seed material, or raw secrets |
| The policy decision and the reason | Full authentication tokens |
| Transaction identifiers and amounts | Anything that would let a reader reconstruct a key |
| Approvals with approver identity | Unredacted webhook secrets |
| Key rotation and revocation events | Personal data beyond what the record needs |
Audit logs are evidence. Ship them to storage that the signing process cannot modify, and alert on the events that should be rare: a signature outside business hours, a policy override, a new destination.
A key compromise runbook
Plan for the worst case while calm. The first hour of a suspected compromise is not the time to design a procedure.
- 01
Contain
Disable the signing endpoint or revoke the credential. Stopping further use matters more than understanding how it happened.
- 02
Preserve evidence
Snapshot logs and note the time. Do not rebuild the environment yet.
- 03
Assess exposure
What could the key sign, and what has it signed since the suspected time?
- 04
Move remaining funds
To a newly generated destination controlled under the new procedure, if the policy requires it.
- 05
Rotate everything downstream
Anything that trusted the old key or that shared its storage.
- 06
Review and record
A blameless review with actions and dates, and a note for counsel where appropriate.
Rehearse this. A tabletop exercise once or twice a year exposes missing contact details and unclear authority far more cheaply than a real incident.
Closing
The implementation follows the sentence. If you can draw the boundary on a whiteboard and defend it to both counsel and the on-call engineer, you have software. If you cannot, you have a service with a nicer interface.
References & further reading
- 1NIST SP 800-57 Part 1 Rev. 5: Recommendation for Key Management — Elaine Barker, NIST, 2020
- 2Secrets Management Cheat Sheet — OWASP Cheat Sheet Series
- 3Vault documentation — HashiCorp
- 4AWS Key Management Service: Overview — AWS Documentation
- 5Cryptographic Storage Cheat Sheet — OWASP Cheat Sheet Series
- 6Separation of duty — NIST Computer Security Resource Center glossary
The product behind this post
StablePay
Self-hosted stablecoin payment infrastructure.
From $4,800 one-time license
About the authors
Layla Haddad
Principal Engineer, Payments Infrastructure
Designs the gateway architecture behind StablePay: state machines, indexing, and signing boundaries.
Mohamed Saad
Smart Contract Developer
Works on the on-chain side of payments: token transfers, event interfaces, and contract review for products that touch a chain.
Product behaviour described here reflects what is implemented and tested; anything else is marked as planned. Code samples are illustrative.
All writing