REX Pay — Deployment Guide
This guide covers production deployment of REX Pay (Regen Exchange), a non-custodial crypto payment processing platform. It is intended for system administrators and DevOps engineers.
Table of Contents
- Prerequisites
- What You Need — No API Keys Required for DOGE/LTC
- Production Setup
- Chain Node Setup
- Scaling
- Monitoring
- Backup and Recovery
- Environment Variables Reference
1. Prerequisites
Software
| Requirement | Minimum Version | Notes |
|---|---|---|
| Docker Engine | 24+ | Required for all services |
| Docker Compose | v2.20+ | Plugin version (docker compose) preferred |
| Git | Any | To clone the repository |
Hardware (Production Recommendations)
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 4 cores | 8+ cores |
| RAM | 8 GB | 16 GB |
| Disk (OS + app) | 20 GB | 50 GB SSD |
| Disk (DOGE mainnet) | 60 GB | 100 GB SSD |
| Disk (LTC mainnet) | 120 GB | 180 GB SSD |
| Network | 10 Mbps | 100 Mbps |
Both blockchain nodes require dedicated SSD storage separate from the OS volume. A single disk setup is possible but not recommended for production.
Domain and TLS
- A domain pointed at your server (rexpay.co is planned for Q2 2026).
- A TLS certificate — Let's Encrypt via Certbot is the standard free option.
- A reverse proxy (Nginx) to terminate TLS and route traffic to Docker containers.
2. What You Need
REX Pay is designed to minimize third-party dependencies and API costs. The table below summarizes exactly what external services are needed and their cost.
| Chain / Service | Node Required | External API | Cost | Notes |
|---|---|---|---|---|
| Dogecoin (DOGE) | Dogecoin Core (included in Docker) | None | Free | Runs its own full node. No key needed. |
| Litecoin (LTC) | Litecoin Core (included in Docker) | None | Free | Runs its own full node. No key needed. |
| USDT TRC-20 | None | TronGrid | Free tier | No node. Requires TronGrid API (free tier is sufficient for low volume). Key recommended for production. Get key at https://www.trongrid.io/ |
| Exchange rates | None | CoinGecko | Free | Public API for DOGE/LTC to fiat conversion. No key needed for the free tier (300 req/min). |
| PostgreSQL | Included in Docker | None | Free | |
| Redis | Included in Docker | None | Free |
Banking and licensing: REX Pay is non-custodial — crypto goes directly to the merchant's own wallet. The platform never holds customer funds. No money transmitter license is required for non-custodial operation. No bank account is needed.
3. Production Setup
3.1 Clone the Repository
3.2 Create the Environment File
Copy the example and fill in production values:
The critical values to set before first boot:
# 1. Generate a Django secret key (use a long random string)
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(50))")
# 2. Generate the Fernet encryption key (CRITICAL — back this up)
REGEN_PAY_ENC_KEY=$(python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
echo "SECRET_KEY=$SECRET_KEY"
echo "REGEN_PAY_ENC_KEY=$REGEN_PAY_ENC_KEY"
Update .env with both values.
CRITICAL: The
REGEN_PAY_ENC_KEYencrypts all merchant xpubs, addresses, and webhook secrets in the database. If this key is lost, all encrypted data is permanently unrecoverable. Back it up to a secure secrets manager immediately.
3.3 Configure Required Environment Variables
Minimum production .env values:
# Django
SECRET_KEY=<generated above — long random string>
DEBUG=false
ALLOWED_HOSTS=rexpay.co,www.rexpay.co
# Database
DATABASE_URL=postgres://regenpay:<strong_password>@postgres:5432/regenpay
# Redis
REDIS_URL=redis://redis:6379/0
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
# Encryption (CRITICAL — back this up)
REGEN_PAY_ENC_KEY=<generated above>
FIELD_ENCRYPTION_KEY=<same value as REGEN_PAY_ENC_KEY>
# CORS
CORS_ALLOWED_ORIGINS=https://rexpay.co,https://www.rexpay.co,https://pay.rexpay.co,https://api.rexpay.co,https://app.rexpay.co,https://docs.rexpay.co
# Public URLs
PUBLIC_API_BASE_URL=https://api.rexpay.co
PUBLIC_CHECKOUT_BASE_URL=https://pay.rexpay.co
SITE_URL=https://rexpay.co
# Dogecoin (mainnet RPC port is 22555)
DOGE_NETWORK=mainnet
DOGE_RPC_HOST=rexpay_dogecoind
DOGE_RPC_PORT=22555
DOGE_RPC_USER=<choose a strong rpc user>
DOGE_RPC_PASSWORD=<choose a strong rpc password>
DOGE_RPC_WALLET=rexpay_watch
# Litecoin (mainnet RPC port is 9332)
LTC_NETWORK=mainnet
LTC_RPC_HOST=rexpay_litecoind
LTC_RPC_PORT=9332
LTC_RPC_USER=<choose a strong rpc user>
LTC_RPC_PASSWORD=<choose a strong rpc password>
LTC_RPC_WALLET=rexpay_watch
# TRON / USDT TRC-20
TRON_NETWORK=mainnet
TRONGRID_API_KEY=<your key from trongrid.io — optional for low volume>
3.4 Update PostgreSQL Password in docker-compose.yml
Update the postgres service environment in docker-compose.yml to match the password set in DATABASE_URL:
postgres:
environment:
- POSTGRES_USER=regenpay
- POSTGRES_PASSWORD=<strong_password>
- POSTGRES_DB=regenpay
3.5 Update Dogecoin and Litecoin Node RPC Credentials
In docker-compose.yml, update the dogecoind and litecoind command arguments to match the RPC user and password set in .env:
dogecoind:
command: >
dogecoind
-mainnet
-server
-rpcuser=<your_rpc_user>
-rpcpassword=<your_rpc_password>
-rpcallowip=172.16.0.0/12
-rpcbind=0.0.0.0
-rpcport=8332
-txindex=1
-disablewallet=0
For production, restrict
-rpcallowipto the Docker internal network range rather than0.0.0.0/0.
3.6 Start All Services
Check that all services started:
3.7 Run Initial Database Migration
The API container runs python manage.py migrate automatically on startup. You can also run it manually:
3.8 Collect Static Files
3.9 Create the First Merchant
REX Pay ships with management commands to bootstrap merchants and API keys:
# Create a merchant account
docker compose exec api python manage.py create_merchant \
--name "My Store" \
--email "merchant@example.com"
# Create API keys (test and live) for the merchant
docker compose exec api python manage.py create_apikey \
--merchant-email "merchant@example.com" \
--mode test
docker compose exec api python manage.py create_apikey \
--merchant-email "merchant@example.com" \
--mode live
The full key is shown only once at creation time. Store it securely.
3.10 Nginx Reverse Proxy with TLS
Install Nginx and Certbot on the host (outside Docker):
Example Nginx configuration for the API:
server {
listen 443 ssl;
server_name api.rexpay.co;
ssl_certificate /etc/letsencrypt/live/rexpay.co/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rexpay.co/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 443 ssl;
server_name pay.rexpay.co;
ssl_certificate /etc/letsencrypt/live/rexpay.co/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rexpay.co/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
4. Chain Node Setup
4.1 Dogecoin Core
Image: dogecoin/dogecoin:latest
Testnet RPC port: 44555 Mainnet RPC port: 8332
Testnet vs Mainnet:
| Setting | Testnet | Mainnet |
|---|---|---|
-testnet flag |
Present | Absent (default) |
| RPC port | 44555 | 8332 |
| Address prefix | n |
D |
| Disk usage | ~1 GB | ~50 GB |
| Sync time | ~30 minutes | ~2-4 hours |
| Coins | Worthless test coins | Real DOGE |
Switching from testnet to mainnet in docker-compose.yml:
And in .env:
Watch-only wallet: The system automatically creates and loads a watch-only wallet named regenpay_watch (configured via DOGE_RPC_WALLET). This wallet tracks all derived payment addresses without holding any private keys.
Volume: Dogecoin blockchain data is stored in the dogecoin_data Docker volume. Do not delete this volume — full resync takes hours.
4.2 Litecoin Core
Image: litecoin/litecoin-core:latest
Testnet RPC port: 19332 Mainnet RPC port: 9332
Testnet vs Mainnet:
| Setting | Testnet | Mainnet |
|---|---|---|
-testnet flag |
Present | Absent |
| RPC port | 19332 | 9332 |
| Address prefixes | m, n, tltc1 |
L, M, ltc1 |
| Disk usage | ~2 GB | ~100 GB |
| Sync time | ~1 hour | ~4-8 hours |
| Default confirmations required | 3 | 3 |
Switching to mainnet in docker-compose.yml:
And in .env:
4.3 USDT TRC-20 (TronGrid API — No Node Required)
REX Pay uses TronGrid's HTTP API to monitor USDT TRC-20 transfers. No local TRON node is required.
Free tier: Sufficient for development and low-volume production. No API key needed to make basic requests.
Production key: Recommended to avoid rate limits. Obtain at https://www.trongrid.io/
Endpoints used:
- /v1/accounts/{address}/transactions/trc20 — poll inbound USDT transfers per address
- /v1/transactions/{txid}/events — retrieve transaction details
- /wallet/getnowblock — health check
Mainnet vs Testnet (Nile):
| Setting | Mainnet | Testnet (Nile) |
|---|---|---|
TRON_NETWORK |
mainnet |
testnet |
| API URL | https://api.trongrid.io |
https://nile.trongrid.io |
| USDT contract | TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t |
TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj |
5. Scaling
5.1 API Workers
The api service runs Gunicorn with 2 workers by default. For production under load, increase the worker count:
api:
command: >
sh -c "python manage.py migrate &&
gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 4 --worker-class gthread --threads 2"
A common rule of thumb is (2 * CPU_cores) + 1 workers.
5.2 Per-Chain Celery Workers
Each chain runs in an independent Celery worker consuming a dedicated queue:
| Worker | Queue | Dependency |
|---|---|---|
celery-doge |
doge |
Dogecoin Core node |
celery-ltc |
ltc |
Litecoin Core node |
celery-tron |
usdt_trc20 |
TronGrid API |
celery-webhooks |
webhooks |
None |
celery-beat |
(scheduler) | All queues |
Each worker can be scaled independently. To add concurrency for a busy chain:
5.3 PostgreSQL Tuning
For production, tune PostgreSQL via environment variables or a custom postgresql.conf. Key settings for a 16 GB RAM server:
max_connections = 100
shared_buffers = 4GB
effective_cache_size = 12GB
maintenance_work_mem = 1GB
checkpoint_completion_target = 0.9
wal_buffers = 64MB
default_statistics_target = 100
random_page_cost = 1.1 # SSD
effective_io_concurrency = 200 # SSD
work_mem = 10MB
Mount a custom config:
postgres:
volumes:
- ./postgres.conf:/etc/postgresql/postgresql.conf
- postgres_data:/var/lib/postgresql/data
command: postgres -c config_file=/etc/postgresql/postgresql.conf
5.4 Redis Configuration
Redis is used for both Celery broker and Django cache. For production, enable persistence:
redis:
command: redis-server --appendonly yes --maxmemory 2gb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
5.5 Multiple API Instances (Load Balancing)
The API is stateless. To run multiple instances behind a load balancer:
- Remove port mapping from the
apiservice indocker-compose.yml. - Add an
nginxortraefikservice as the load balancer. - Scale:
docker compose up -d --scale api=3
6. Monitoring
6.1 Health Check Endpoints
| Endpoint | Purpose | Returns unhealthy on |
|---|---|---|
GET /health/ |
Overall system health (DB, Redis, chain providers) | DB failure → 503 |
GET /health/ready/ |
Kubernetes readiness probe | DB unreachable → 503 |
GET /health/live/ |
Kubernetes liveness probe | Always 200 if process is alive |
Example /health/ response:
{
"status": "healthy",
"checks": {
"database": "ok",
"cache": "ok",
"chains": {
"DOGE": {
"core": { "name": "dogecoin_core", "healthy": true },
"fallback": { "enabled": false, "name": null, "healthy": null },
"using_fallback": false,
"degraded": false
}
}
}
}
A status of degraded means the system is operational but a chain provider is using a fallback or experiencing issues. unhealthy (503) means the database is down and the system cannot function.
6.2 Celery Monitoring
View worker status:
docker compose exec celery-doge celery -A core inspect active
docker compose exec celery-doge celery -A core inspect stats
View beat schedule:
6.3 Beat Schedule Summary
| Task | Schedule | Queue | Purpose |
|---|---|---|---|
watch_all_chains |
Every 10 seconds | doge |
Scan chains for new transactions |
update_confirmations |
Every 30 seconds | doge |
Update confirmation counts |
check_expired |
Every 60 seconds | doge |
Expire unpaid invoices |
retry_failed_webhooks |
Every 60 seconds | webhooks |
Retry failed webhook deliveries |
cleanup_old_intents |
Sundays at 3:00 AM | doge |
Log old terminal-state intents for archival |
6.4 Log Aggregation
Stream all service logs:
Stream a specific service:
For production, forward logs to a centralized system (Loki, CloudWatch, Datadog, etc.) by configuring Docker's logging driver in docker-compose.yml:
7. Backup and Recovery
7.1 PostgreSQL Backups
Daily backup script:
#!/bin/bash
BACKUP_DIR=/backups/postgres
DATE=$(date +%Y-%m-%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
docker compose exec -T postgres pg_dump \
-U regenpay \
-d regenpay \
--format=custom \
--compress=9 \
> "$BACKUP_DIR/regenpay-$DATE.dump"
# Retain 30 days
find "$BACKUP_DIR" -name "*.dump" -mtime +30 -delete
Restore:
docker compose exec -T postgres pg_restore \
-U regenpay \
-d regenpay \
--clean \
< /backups/postgres/regenpay-<date>.dump
7.2 Chain Node Data Volumes
The Dogecoin and Litecoin blockchain data lives in Docker named volumes (dogecoin_data, litecoin_data). These are large and slow-changing. Options:
- No backup needed if you are willing to re-sync from genesis on disaster recovery (hours, not days for DOGE/LTC).
- Snapshot the volume using Docker or the host's filesystem snapshot feature.
- Use a separate fast disk mounted as the volume path, then snapshot that disk.
To find the volume path on the host:
7.3 Redis Backup
Redis persistence is handled by the appendonly yes configuration (see Section 5.4). The AOF file is stored in the redis_data volume. Back it up like any file.
For Celery's usage, Redis data loss is acceptable — tasks will be re-queued on the next beat tick (within 10-60 seconds). Rate limit counters and cache will reset harmlessly.
7.4 Encryption Key Backup — CRITICAL
The REGEN_PAY_ENC_KEY (Fernet key) encrypts all sensitive merchant data in the database:
- Merchant xpubs (extended public keys for address derivation)
- Settlement addresses
- Webhook signing secrets
If this key is lost, ALL encrypted data in the database is permanently unrecoverable. Merchant configurations cannot be restored.
Required backup procedure:
- Store the key in at minimum two separate locations (e.g., a password manager plus an offline printed copy in a safe).
- Rotate it only with a planned key-rotation migration — never change it without migrating the encrypted data first.
- Include it in your secrets management system (HashiCorp Vault, AWS Secrets Manager, etc.).
8. Environment Variables Reference
Core Django Settings
| Variable | Default | Required | Description |
|---|---|---|---|
SECRET_KEY |
dev-secret-key-change-in-production |
Yes | Django secret key. Must be long, random, and unique per environment. |
DEBUG |
true |
Yes | Set to false in production. |
ALLOWED_HOSTS |
localhost,127.0.0.1,regenpay.local |
Yes | Comma-separated list of allowed hostnames. |
Database
| Variable | Default | Required | Description |
|---|---|---|---|
DATABASE_URL |
postgres://regenpay:regenpay@localhost:5432/regenpay |
Yes | PostgreSQL connection URL. |
Redis and Celery
| Variable | Default | Required | Description |
|---|---|---|---|
REDIS_URL |
redis://localhost:6379/0 |
Yes | Redis connection URL. Used for cache and session. |
CELERY_BROKER_URL |
redis://localhost:6379/0 |
Yes | Celery message broker URL. |
CELERY_RESULT_BACKEND |
redis://localhost:6379/0 |
Yes | Celery result backend URL. |
CORS
| Variable | Default | Required | Description |
|---|---|---|---|
CORS_ALLOWED_ORIGINS |
http://localhost:3000,http://127.0.0.1:3000 |
Yes | Comma-separated list of allowed CORS origins. |
Encryption
| Variable | Default | Required | Description |
|---|---|---|---|
REGEN_PAY_ENC_KEY |
(empty) | Yes | Fernet key for encrypting xpubs, addresses, and webhook secrets. Generate with python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())". |
FIELD_ENCRYPTION_KEY |
(empty) | Yes | Must be set to the same value as REGEN_PAY_ENC_KEY. |
Dogecoin Core RPC
| Variable | Default | Required | Description |
|---|---|---|---|
DOGE_NETWORK |
testnet |
Yes | mainnet or testnet. |
DOGE_RPC_HOST |
localhost |
Yes | Hostname of the Dogecoin Core node. Use dogecoind inside Docker. |
DOGE_RPC_PORT |
44555 |
Yes | RPC port. 44555 for testnet, 8332 for mainnet. |
DOGE_RPC_USER |
rpcuser |
Yes | RPC username. Must match node's -rpcuser argument. |
DOGE_RPC_PASSWORD |
rpcpassword |
Yes | RPC password. Must match node's -rpcpassword argument. |
DOGE_RPC_TIMEOUT |
30.0 |
No | RPC call timeout in seconds. |
DOGE_RPC_WALLET |
regenpay_watch |
No | Watch-only wallet name. Auto-created on first start. |
Litecoin Core RPC
| Variable | Default | Required | Description |
|---|---|---|---|
LTC_NETWORK |
testnet |
Yes | mainnet or testnet. |
LTC_RPC_HOST |
litecoind |
Yes | Hostname of the Litecoin Core node. Use litecoind inside Docker. |
LTC_RPC_PORT |
19332 |
Yes | RPC port. 19332 for testnet, 9332 for mainnet. |
LTC_RPC_USER |
rpcuser |
Yes | RPC username. Must match node's -rpcuser argument. |
LTC_RPC_PASSWORD |
rpcpassword |
Yes | RPC password. |
LTC_RPC_TIMEOUT |
30.0 |
No | RPC call timeout in seconds. |
LTC_RPC_WALLET |
regenpay_watch |
No | Watch-only wallet name. Auto-created on first start. |
TRON / USDT TRC-20
| Variable | Default | Required | Description |
|---|---|---|---|
TRON_NETWORK |
mainnet |
Yes | mainnet or testnet (Nile). |
TRONGRID_API_KEY |
(empty) | No | TronGrid API key. Optional for low volume; recommended for production. Get at https://www.trongrid.io/ |
Rate Service (CoinGecko)
| Variable | Default | Required | Description |
|---|---|---|---|
COINGECKO_API_URL |
https://api.coingecko.com/api/v3 |
No | CoinGecko base URL. |
RATE_CACHE_TTL_SECONDS |
300 |
No | How long to cache exchange rates in Redis (seconds). |
Webhook Delivery
| Variable | Default | Required | Description |
|---|---|---|---|
WEBHOOK_MAX_RETRIES |
8 |
No | Maximum delivery attempts per event. |
WEBHOOK_RETRY_SCHEDULE |
60,300,1800,7200,18000,36000,86400 |
No | Comma-separated retry delays in seconds. |
WEBHOOK_JITTER_PERCENT |
15 |
No | Jitter percentage applied to retry delays. |
WEBHOOK_TIMESTAMP_TOLERANCE |
300 |
No | Seconds of tolerance for webhook signature timestamp validation. |
Rate Limiting
| Variable | Default | Required | Description |
|---|---|---|---|
RATE_LIMIT_LIVE_PER_MINUTE |
60 |
No | Requests per minute for live API keys. |
RATE_LIMIT_TEST_PER_MINUTE |
120 |
No | Requests per minute for test API keys. |
RATE_LIMIT_CHECKOUT_PER_MINUTE |
30 |
No | Requests per minute for unauthenticated checkout polling (per IP). |
Public URLs
| Variable | Default | Required | Description |
|---|---|---|---|
PUBLIC_API_BASE_URL |
http://localhost:8000 |
Yes | Externally reachable API base URL. Used to construct links. |
PUBLIC_CHECKOUT_BASE_URL |
http://localhost:3000 |
Yes | Externally reachable checkout frontend base URL. Used for checkout_url in API responses. |
SITE_URL |
http://localhost:8000 |
No | Main site URL. |
Idempotency
| Variable | Default | Required | Description |
|---|---|---|---|
IDEMPOTENCY_KEY_TTL_HOURS |
24 |
No | How long idempotency keys are valid (hours). |
Fallback Provider (Optional)
| Variable | Default | Required | Description |
|---|---|---|---|
FALLBACK_PROVIDER |
none |
No | External fallback for DOGE if Core is unhealthy. Options: none, sochain. |
FALLBACK_PROVIDER_ENABLED |
false |
No | Enable the fallback provider. |
DOGE_EXPLORER_API_URL |
https://sochain.com/api/v2 |
No | SoChain API base URL (if fallback enabled). |
DOGE_EXPLORER_API_KEY |
(empty) | No | SoChain API key (if required). |