Self-hosted Bitcoin payment processor.
No third parties. No monthly fees. Full control of your keys.
Live Demo · Test a Donation · Website · Quick Start · API Reference · Configuration · Deployment · License
Alpha Software – BTPay is in early alpha and under active development. Things may change. Please test thoroughly before using in production and keep regular backups.
A lightweight, privacy-focused alternative to BTCPay Server.
Q: Why is the name so similar to BTCPay?
A: My hope is the confusion makes Nicolas 🫶 and Kukks 🫶 learn Python and adopt this. Just kidding, what they built is amazing. They will never learn to python. So we will keep this alternative running for the very few who need it done correctly but with a much smaller subset of features. The real asnwer the is pip and domains were available. Please consider checking out their project BTCPS and donating to OpenSats
/s/<slug> links, no authentication required for buyers# Clone
git clone https://github.com/btpay-org/btpay.git
cd btpay
# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install (reproducible)
pip install --require-hashes -r requirements.lock
pip install -e . --no-deps
# Or: install latest (unpinned)
# pip install -e ".[dev]"
# Create admin user
flask --app app user-create \
--email you@example.com \
--password your-secure-password \
--first-name Admin \
--last-name User
# Run
python app.py
Open http://localhost:5000 in your browser.
BTPay uses config_default.py for defaults. Override settings by creating a config.py in the project root or via environment variables prefixed with BTPAY_. See the full Configuration Guide.
| Setting | Env Var | Default | Description |
|---|---|---|---|
SECRET_KEY |
BTPAY_SECRET_KEY |
(dev default) | Flask secret key — change in production |
REFNUM_KEY |
BTPAY_REFNUM_KEY |
(dev default) | NaCl SecretBox key for reference numbers (32 bytes hex) |
REFNUM_NONCE |
BTPAY_REFNUM_NONCE |
(dev default) | NaCl SecretBox nonce for reference numbers (24 bytes hex) |
| Setting | Default | Description |
|---|---|---|
BTC_QUOTE_DEADLINE |
30 |
Minutes to lock the BTC exchange rate |
BTC_MARKUP_PERCENT |
0 |
Markup percentage on the exchange rate |
MAX_UNDERPAID_GIFT |
5 |
USD threshold to accept small underpayments |
EXCHANGE_RATE_SOURCES |
['coingecko', 'coinbase', 'kraken'] |
Rate source APIs |
EXCHANGE_RATE_INTERVAL |
300 |
Seconds between rate fetches |
BTC_CONFIRMATION_THRESHOLDS |
[(100,1), (1000,3), (None,6)] |
Amount (USD) to required confirmations |
| Setting | Env Var | Default | Description |
|---|---|---|---|
SOCKS5_PROXY |
BTPAY_SOCKS5_PROXY |
(empty) | SOCKS5 proxy for Tor (e.g. socks5h://127.0.0.1:9050) |
MEMPOOL_API_URL |
BTPAY_MEMPOOL_URL |
https://mempool.space/api |
Can point to your own instance |
| Setting | Env Var | Default | Description |
|---|---|---|---|
SMTP_CONFIG.host |
BTPAY_SMTP_HOST |
(empty) | SMTP server hostname |
SMTP_CONFIG.port |
BTPAY_SMTP_PORT |
587 |
SMTP port (587=TLS, 465=SSL) |
SMTP_CONFIG.username |
BTPAY_SMTP_USER |
(empty) | SMTP username |
SMTP_CONFIG.password |
BTPAY_SMTP_PASS |
(empty) | SMTP password |
SMTP_CONFIG.from_email |
BTPAY_SMTP_FROM |
(empty) | From email address |
See config_default.py for the complete list of all configuration options.
# User management
flask --app app user-create # Create admin user (interactive)
flask --app app user-list # List all users
flask --app app user-reset-totp --email user@example.com # Reset 2FA
# Wallet management
flask --app app wallet-create --org-id 1 --name "Main Wallet" --type xpub --xpub "zpub..."
flask --app app wallet-import --wallet-id 1 --file addresses.txt
# Data management
flask --app app db-export [output_dir] # Export data to JSON
flask --app app db-import [input_dir] # Import data from JSON
flask --app app db-backup # Create timestamped backup
flask --app app db-stats # Show storage statistics
# Exchange rates
flask --app app rates # Show current rates
# Software updates
flask --app app check-updates # Check for new versions
flask --app app update --version v0.2.0 # Update via git
flask --app app update --zip release.zip # Update from ZIP (air-gapped)
flask --app app update-rollback # Rollback to previous version
BTPay supports three wallet types:
Provide your hardware wallet’s extended public key. BTPay derives fresh addresses using BIP32. Supports:
xpub... — P2PKH (legacy, 1... addresses)ypub... — P2SH-P2WPKH (wrapped segwit, 3... addresses)zpub... — P2WPKH (native segwit, bc1q... addresses)Standard output descriptors for precise script control:
wpkh([fingerprint/path]xpub.../0/*) — native segwitsh(wpkh([fingerprint/path]xpub.../0/*)) — wrapped segwitpkh([fingerprint/path]xpub.../0/*) — legacyImport a plain text file with one Bitcoin address per line. Useful for pre-generated addresses or cold storage setups.
Create public-facing product stores and donation pages — inspired by BTCPay Server’s “Apps” feature.
Set up a product catalog with fixed prices. Buyers browse at /s/<your-slug>, pick items, and check out through the standard invoice flow. Supports:
Create a donation/tip page with preset amounts (e.g. $5, $10, $25, $50, $100) and optional custom amounts. Supports:
https://your-instance.com/s/<slug>Purchases and donations create invoices automatically using your configured payment methods (Bitcoin, stablecoins, wire, BTCPay, LNbits). Inventory and revenue stats update after payment confirmation.
Full REST API at /api/v1/. Authenticate with Bearer tokens.
Go to Settings > API Keys and create a new key. The raw key is shown once — save it.
curl -X POST http://localhost:5000/api/v1/invoices \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_email": "customer@example.com",
"customer_name": "Alice",
"currency": "USD",
"lines": [
{"description": "Widget", "quantity": 2, "unit_price": "49.99"}
]
}'
curl http://localhost:5000/api/v1/invoices/INV-0001/status \
-H "Authorization: Bearer YOUR_API_KEY"
See the complete API Reference for all endpoints.
Register webhook endpoints to receive real-time notifications:
curl -X POST http://localhost:5000/api/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yoursite.com/webhook",
"events": ["invoice.paid", "invoice.confirmed"]
}'
Events: invoice.created, invoice.paid, invoice.confirmed, invoice.expired, invoice.cancelled, payment.received, payment.confirmed, or * for all.
Payloads are signed with HMAC-SHA256 via the X-BTPay-Signature header. Verify with the secret returned when creating the endpoint.
See the full Deployment Guide.
# Install
cd /opt/btpay
python3 -m venv .venv
source .venv/bin/activate
make install-locked
# Create config.py with production secrets
cat > config.py << 'EOF'
SECRET_KEY = 'your-random-64-char-hex-string'
REFNUM_KEY = 'your-random-64-char-hex-string'
REFNUM_NONCE = 'your-random-48-char-hex-string'
DEV_MODE = False
EOF
# Create admin user
flask --app app user-create
# Run with gunicorn
gunicorn -c deploy/gunicorn.conf.py wsgi:app
Pre-built configs are included:
deploy/gunicorn.conf.py — Gunicorn with gthread workerdeploy/nginx.conf — Nginx reverse proxy with TLSdeploy/btpay.service — systemd service with security hardeningBTPay uses an in-memory data store. You must run gunicorn with a single worker (workers = 1). The default config handles this. Multiple threads are fine (threads = 4).
Data is stored as JSON files in the data/ directory:
Back up the data/ directory regularly. Use flask db-backup or make backup for manual backups.
# Run tests
make test
# Run tests with coverage
make test-cov
# Run dev server
make run
859 user story tests and unit tests covering ORM, auth, Bitcoin key derivation, invoicing, storefronts, API, webhooks, email, frontend, account security, Electrum server selection, stablecoin monitoring, BTCPay Server, LNbits, and security middleware.
btpay/
app.py # Flask app factory
config_default.py # Default configuration
wsgi.py # Gunicorn entry point
btpay/
orm/ # In-memory ORM (engine, model, query, columns, persistence)
auth/ # Authentication (models, sessions, totp, decorators, views)
bitcoin/ # Bitcoin (xpub, descriptors, address pool, exchange, electrum, mempool, monitor)
connectors/ # Payment connectors (BTCPay Server, LNbits, stablecoins, EVM RPC, wire)
invoicing/ # Invoicing (models, service, checkout, payment methods, PDF, wire)
storefront/ # Storefronts & donations (models, admin views, public views, fulfillment)
api/ # REST API (routes, serializers, webhooks)
frontend/ # Web UI (dashboard, invoices, checkout, settings, filters)
security/ # Security (tokens, hashing, crypto, validators, rate limit, csrf, middleware)
email/ # Email (service, templates)
templates/ # Jinja2 templates
static/ # CSS, JS, images
tests/ # 859 tests
deploy/ # gunicorn, nginx, systemd configs
859 tests across 14 test modules:
| Module | Tests | Covers |
|---|---|---|
test_user_stories |
378 | End-to-end user flows, storefronts, and integration scenarios |
test_bitcoin |
102 | xpub derivation, descriptors, address pool, exchange rates, Electrum, mempool |
test_connectors |
67 | Stablecoin monitoring, EVM RPC, wire transfers |
test_auth |
60 | Login, sessions, TOTP 2FA, account lockout, password changes |
test_invoicing |
58 | Invoice lifecycle, payment methods, PDF generation |
test_api |
52 | REST API endpoints, authentication, webhooks |
test_orm |
35 | In-memory ORM engine, models, queries, columns, persistence |
test_btcpay |
25 | BTCPay Server connector |
test_lnbits |
24 | LNbits connector |
test_phase7 |
21 | Security middleware, hack detection, rate limiting |
test_security_fixes |
19 | Security hardening and vulnerability fixes |
test_refnums |
9 | NaCl SecretBox-encrypted reference numbers |
test_tokens |
5 | Token hashing and verification |
test_chrono |
4 | Date/time utilities |
# Run all tests
make test
# Run tests with coverage report
make test-cov
THIS SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
BTPay handles financial transactions. The authors and contributors are not responsible for any loss of funds, data, or business arising from the use of this software. You are solely responsible for securing your deployment, keys, and data. Use at your own risk.