crypto-processing-api¶
The accounting layer BTCPay Server does not have. Run it next to your own BTCPay node and your platform gets per-user crypto balances, deposits and withdrawals through a small authenticated JSON API — backed by an append-only double-entry ledger that a webhook cannot corrupt, a reconciliation sweep that does not trust the fast path, and a threat model that starts from "the hot wallet is the loss ceiling". Self-hosted, single-tenant, MIT.
Your platform calls it backend-to-backend. This service owns the money ledger; BTCPay owns the blockchain. It runs on one small VPS next to BTCPay — built for a €4/month Hetzner CAX11.
These docs track main
There is no version selector. For the documentation matching a release you
have deployed, read docs/ at that release's tag on GitHub. A version
selector will arrive with 1.0, when there is more than one supported
version to select between.
Where to go¶
| If you are | Start at |
|---|---|
| trying it out | Quickstart — regtest in about ten minutes |
| writing the platform that calls it | Integrating, then the client libraries |
| putting it on a server | Deploying and the security model |
| holding the pager | the runbooks and backups |
| adding an asset | Adding your own asset |
| looking something up | API endpoints, configuration, webhook events |
| asking why it is built this way | the design record |
Who this is for¶
- You need per-user custodial balances in BTC or USDT — a marketplace, a game economy, SaaS credits — and "one invoice, one payment" is not enough.
- You are willing to run BTCPay Server and one VPS yourself.
- You want the money-correctness machinery already built: idempotency, double-entry accounting, approval queues, velocity caps, and a reconciliation job that finds what the fast path missed.
Who this is not for¶
- You want non-custodial checkout. Then you want BTCPay on its own. Adding this means you now hold customer funds, which is a legal and operational position, not a feature.
- You want zero operations. A hosted processor takes the work by taking your float. That is a real trade and sometimes the right one.
- You need one BTC balance spendable over either rail.
BTCandBTC_LNare separate floats here, and merging them is not planned. - You need multi-tenancy, many assets, or something audited at scale. One deployment serves one platform, three assets ship, and there has been no external audit.
How it compares¶
| Raw BTCPay Server | Hosted processor | This project | Building it yourself | |
|---|---|---|---|---|
| Per-user ledger and balances | none — invoices only | their database, their rules | double-entry, append-only, yours | months of subtle work |
| Custody | you | them | you | you |
| Fees | node costs | a percentage of volume | node + VPS costs | your time |
| KYC / account risk | none | their policies; they can freeze | none | none |
| Withdrawal controls | manual | theirs | holds, velocity caps, approval queue | build them |
| Correctness under crash and retry | not applicable | opaque | idempotency + a reconciliation sweep, drilled on regtest | the hard part |
| Audit trail | an invoice list | a CSV export | an immutable journal in your PostgreSQL | build it |
| Trust required | your node | a third party holding your float | your node + this code (MIT, readable) | your code |
The honest row is custody. This project does not remove custody risk — it gives you the controls and the books. A hosted processor removes the work by taking the float. Pick the one whose risk you would rather hold.
What it does¶
| Deposits | Withdrawals | |
|---|---|---|
| BTC (on-chain, pruned node) | automatic | automatic via BTCPay's payout processor |
| BTC_LN (Lightning, opt-in) | automatic, instant | automatic via BTCPay's Lightning payout processor |
| USDT-TRC20 (USDt plugin + TronGrid) | automatic | operator-sent, verified on chain |
Read this before \"we need Lightning\" becomes a yes
BTC_LN is a separate asset with its own float. A user has an on-chain
BTC balance and a Lightning BTC balance, and they are not interchangeable:
Lightning funds cannot be withdrawn on chain, on-chain funds cannot be
withdrawn over Lightning, and nothing moves between them automatically.
Moving value between the two floats is an operator action —
the rebalancing runbook.
Most people who say they need Lightning mean one balance, either rail
out. This is not that. It is off by default (LIGHTNING_ENABLED=false),
partly for that reason and partly because enabling it makes the bootstrap
request one server-level BTCPay permission — the
security model explains the trade-off.
Status¶
v0.1.1 released; v0.2 in progress on main. The test suite includes
concurrency tests with real threads against real PostgreSQL, property-based
tests over the ledger, and end-to-end drills against a real BTCPay on regtest.
The API and webhook contracts are committed and CI fails if the code and the
specification disagree.
It has not been through an external audit and has not run at scale. Read the security model before pointing real funds at it, and start with amounts you would not mind losing.