btpay

BTPay [BETA]

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. 🫢

Features

Payments

Bitcoin Verification

Invoicing & Checkout

Integrations

Access & Security

Infrastructure

Quick Start

# 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.

Requirements

Configuration

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_.

Essential Settings

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

Bitcoin Settings

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

Privacy Settings

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

Email Settings

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.

CLI Commands

# 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

Wallet Types

BTPay supports three wallet types:

Provide your hardware wallet’s extended public key. BTPay derives fresh addresses using BIP32. Supports:

Output Descriptor

Standard output descriptors for precise script control:

Address List

Import a plain text file with one Bitcoin address per line. Useful for pre-generated addresses or cold storage setups.

API

Full REST API at /api/v1/. Authenticate with Bearer tokens.

Create an API Key

Go to Settings > API Keys and create a new key. The raw key is shown once β€” save it.

Example: Create an Invoice

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"}
    ]
  }'

Example: Get Invoice Status

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.

Webhooks

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.

Production Deployment

See docs/DEPLOYMENT.md for the full production setup guide.

Quick Production Setup

# 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:

Important: Single Worker

BTPay 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 Persistence

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.

Security

Development

# 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.

Architecture

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

Future Plans

License

MIT