Self-hosted Bitcoin payment processor. Accept Bitcoin payments with no third parties, no monthly fees, and full control of your keys.
Built as a lightweight, privacy-focused alternative to BTCPay Server.
Q: Why is the name so simmilar to BTCpay. A: My hope is the confusion makes Nicolas and Kukks learn to python and adopt this. π«Ά
# Clone
git clone https://github.com/nvk/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_.
| Setting | Env Var | Default | Description |
|---|---|---|---|
SECRET_KEY |
BTPAY_SECRET_KEY |
(dev default) | Flask secret key β change in production |
REFNUM_AES_KEY |
BTPAY_REFNUM_AES |
(dev default) | AES key for reference number encryption |
REFNUM_HMAC_KEY |
BTPAY_REFNUM_HMAC |
(dev default) | HMAC key for reference numbers |
| 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
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.
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 docs/API.md for the complete API reference.
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 docs/DEPLOYMENT.md for the full production setup 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_AES_KEY = 'your-random-32-char-hex-string'
REFNUM_HMAC_KEY = 'your-random-32-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
789 user story tests and unit tests covering ORM, auth, Bitcoin key derivation, invoicing, 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)
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
tests/ # 789 tests
deploy/ # gunicorn, nginx, systemd configs
MIT