Skip to content

Configuration reference

Every variable this service reads, generated from the Settings model that reads them. The environment is the only configuration source; a .env file next to the process is read too, and a real environment variable wins over it.

.env.example is the annotated copy to start from — same variables, more operator prose. This page is the one that cannot go out of date.

A required variable has no default and the process will not start without it. Values marked secret are never logged and never printed by any endpoint. Amounts are always integers in the asset's smallest unit — satoshis for BTC and BTC_LN, micro-USDT for USDT_TRC20.

Process

Variable Type Default Notes
ENVIRONMENT development | test | staging | production production Also selects the API key prefix: development and test mint cpk_test_ keys, everything else mints cpk_live_ ones.
DEBUG bool false Verbose errors and internals in responses. Refused unless ENVIRONMENT=development.
LOG_LEVEL str INFO Python logging level for the structlog pipeline.
LOG_JSON bool true One JSON object per line for a log collector; false gives human-readable console output.

Database

Variable Type Default Notes
DATABASE_URL str required — The ledger. Never the same instance as BTCPay's own database.
DB_POOL_SIZE int 5 ≥ 1 — Postgres connections are the scarce resource on a 4GB box shared with BTCPay and bitcoind, so both pool numbers stay small.
DB_MAX_OVERFLOW int 5 ≥ 0 — Connections opened beyond the pool when it is exhausted.
DB_ECHO bool false Echo every SQL statement. Debugging only, and extremely noisy.

HTTP server

Variable Type Default Notes
API_HOST str 0.0.0.0 Bind address inside the container. The API publishes no ports in production; BTCPay and the platform reach it over the Docker network.
API_PORT int 8000 Port the API listens on inside the container.

Idempotency-Key handling

Variable Type Default Notes
IDEMPOTENCY_STALE_SECONDS int 60 ≥ 1 — An in_progress idempotency row older than this may be reclaimed by a retry, so a crash between "row inserted" and "work done" cannot turn into a permanent 409 on a money endpoint.
IDEMPOTENCY_TTL_HOURS int 72 ≥ 1 — How long completed idempotency records are kept before the purge job removes them.

Asset seed values — BTC (satoshis)

Variable Type Default Notes
SEED_BTC_PAYMENT_METHOD str BTC-CHAIN BTCPay's payment method id for on-chain BTC. Version-dependent; the real value is discovered from the store at startup.
SEED_BTC_WITHDRAWAL_AUTO_LIMIT int 500000 ≥ 0 — Withdrawals at or below this gross amount approve themselves. Above it they queue for an admin.
SEED_BTC_WITHDRAWAL_DAILY_CAP int 5000000 ≥ 0 — Rolling 24-hour per-asset cap. Once the window sum reaches it every withdrawal goes to manual approval regardless of size — the control that actually bounds a stolen-API-key attack.
SEED_BTC_WITHDRAWAL_USER_DAILY_CAP int (unset) Optional rolling 24-hour cap per user. Unset disables it.
SEED_BTC_WITHDRAWAL_MIN int 1 ≥ 1 — Smallest accepted gross withdrawal. The dust rule applies on top.
SEED_BTC_WITHDRAWAL_FLAT_FEE int 0 ≥ 0 — Flat service fee charged on top of the estimated miner fee.

Asset seed values — Lightning, BTC_LN (satoshis)

Variable Type Default Notes
LIGHTNING_ENABLED bool false Lightning is off unless a deployment asks for it, and the default is the security decision rather than a convenience. Enabling it means the bootstrap requests btcpay.server.canuseinternallightningnode, which is a server-level BTCPay permission — the one kind this project otherwise refuses to hold. See the security model, threat 5.
SEED_LN_PAYMENT_METHOD str BTC-LN BTCPay's Lightning payment method id.
SEED_LN_WITHDRAWAL_AUTO_LIMIT int 200000 ≥ 0 — The same meaning as the BTC caps, over a different pot of money: channel balance, which the BTC caps say nothing about.
SEED_LN_WITHDRAWAL_DAILY_CAP int 2000000 ≥ 0 — Rolling 24-hour cap on the Lightning float, in satoshis.
SEED_LN_WITHDRAWAL_USER_DAILY_CAP int (unset) Optional rolling 24-hour cap per user. Unset disables it.
SEED_LN_WITHDRAWAL_MIN int 1000 ≥ 1 — Above the flat fee on purpose: a minimum below it would accept requests that deliver nothing and be refused as dust a moment later.
SEED_LN_WITHDRAWAL_FLAT_FEE int 100 ≥ 0 — A flat service charge, because a routing fee cannot be estimated before the route is found. What the route actually cost is journalled at settle time from the node, so this number is revenue and not a cost estimate.
DEPOSIT_INVOICE_EXPIRY_MIN_LN int 30 ≥ 1 — Shorter than the on-chain window on purpose: an open BOLT11 invoice ties up inbound channel liquidity for as long as it lives.
LN_PAYOUT_TIMEOUT_SECONDS int 900 ≥ 60 — How long a Lightning payout may sit in BTCPay's AwaitingPayment before this service cancels it. BTCPay's Lightning processor retries a payout it cannot route for as long as the invoice lives and never moves it to a failed state, so without a timeout the withdrawal stays submitted forever with the user's balance held.

Asset seed values — USDT-TRC20 (micro-USDT)

Variable Type Default Notes
SEED_USDT_PAYMENT_METHOD str USDT_TRC20 BTCPay's payment method id from the USDt plugin. Version-dependent; discovered from the store at startup like the BTC one.
SEED_USDT_WITHDRAWAL_AUTO_LIMIT int 200000000 ≥ 0 — USDT withdrawals are manual-only today, so this limit only starts to matter once an automated TRON signer exists.
SEED_USDT_WITHDRAWAL_DAILY_CAP int 2000000000 ≥ 0 — Rolling 24-hour per-asset cap, in micro-USDT.
SEED_USDT_WITHDRAWAL_USER_DAILY_CAP int (unset) Optional rolling 24-hour cap per user. Unset disables it.
SEED_USDT_WITHDRAWAL_MIN int 1 ≥ 1 — Smallest accepted gross withdrawal, in micro-USDT.
SEED_USDT_WITHDRAWAL_FLAT_FEE int 1000000 ≥ 0 — Flat fee covering the operator's TRX gas for a TRC-20 transfer.

Withdrawal policy

Variable Type Default Notes
WITHDRAWAL_FEE_MODE deduct | absorb deduct deduct — the user receives gross minus the fee. absorb — the user receives gross and the operator pays the fee.
USDT_AUTO_WITHDRAW bool false Refused at startup while it is true: the BTCPay USDt plugin has no payout handler, so nothing could send an auto-approved USDT withdrawal.

Bitcoin network, fee estimation and deposit windows

Variable Type Default Notes
BITCOIN_NETWORK mainnet | testnet | signet | regtest mainnet Which network destination addresses are validated against. A production environment refuses to start on anything but mainnet.
BTC_FEE_TARGET_BLOCKS int 3 ≥ 1 — Confirmation target for the fee estimate, in blocks.
BTC_PAYOUT_VSIZE_VB int 300 ≥ 1 — Assumed payout size in virtual bytes. Raise it if the hot wallet holds many small deposit UTXOs; each extra P2WPKH input is roughly 68 vB.
BTC_FALLBACK_FEE_SAT_PER_VB int 20 ≥ 1 — Used only when BTCPay and mempool.space both fail to answer. Reaching it logs an error, because every withdrawal is then priced off a guess.
BTC_DUST_THRESHOLD_SAT int 546 ≥ 0 — A withdrawal whose net amount lands at or below this is refused before any hold is placed; the output would be unspendable.
MEMPOOL_SPACE_URL str https://mempool.space/api/v1/fees/recommended Second fee source, tried when BTCPay's wallet estimate is unavailable. Unset disables it, which is right for a deployment that must make no outbound calls to third parties.
WITHDRAWAL_SUBMIT_INTERVAL_SECONDS float 10.0 > 0 — How often approved withdrawals are turned into BTCPay payouts.
RECONCILE_WITHDRAWAL_INTERVAL_SECONDS float 60.0 > 0 — Job B: how often submitted and broadcast payouts are re-checked.
STUCK_SUBMITTING_SECONDS int 300 ≥ 30 — How long a withdrawal may sit in submitting with no payout reference before reconciliation works out what happened to it.
DEPOSIT_INVOICE_EXPIRY_MIN_BTC int 60 ≥ 1 — Invoice checkout window. USDT is deliberately not shortened to recycle the address pool: a short window makes a late payment land on another user's reserved address. Size the pool up instead.
DEPOSIT_INVOICE_EXPIRY_MIN_USDT int 60 ≥ 1 — The USDT checkout window. See the note on the BTC one above.
DEPOSIT_MONITORING_MINUTES int 1440 ≥ 1 — How long after expiry BTCPay keeps attributing payments to an invoice, and therefore how long the deposit sweep keeps asking. Must be at least the invoice expiry.

Worker schedule (the reconciliation loop)

Variable Type Default Notes
WEBHOOK_POLL_INTERVAL_SECONDS float 5.0 > 0 — How often the worker drains the webhook_events queue.
WEBHOOK_MAX_ATTEMPTS int 10 ≥ 1 — Attempts before an inbound webhook event is parked for an operator.
RECONCILE_DEPOSIT_INTERVAL_SECONDS float 120.0 > 0 — Job A: re-ask BTCPay about every deposit still worth asking about.
RECONCILE_SETTLED_WINDOW_DAYS int 7 ≥ 1 — Fallback window for polling settled and expired deposits when BTCPay reported no monitoring expiry.
RECONCILE_ORPHAN_SCAN_INTERVAL_SECONDS float 86400.0 > 0 — Nightly page-through of the store's invoices looking for ours that have no local row — the case after restoring an older database backup.
WALLET_SCAN_INTERVAL_SECONDS float 600.0 > 0 — Wallet scan: flags coins that reached the hot wallet matching no deposit payment. The only detector for a payment to an address BTCPay stopped watching.

BTCPay Server

Variable Type Default Notes
BTCPAY_URL str (unset) Greenfield base URL, container-to-container inside the Docker network.
BTCPAY_API_KEY str (unset) secret — Restricted Greenfield API key scoped to the single store. Never server-admin.
BTCPAY_STORE_ID str (unset) The store this service owns.
BTCPAY_WEBHOOK_SECRET str (unset) secret — Webhook signing secret. Must differ from BTCPAY_API_KEY — the ingress path verifies an HMAC over raw bytes with this value.

TRON / USDT-TRC20

Variable Type Default Notes
TRON_NETWORK mainnet | nile mainnet Selects the default TronGrid endpoint and the default USDT contract.
TRONGRID_BASE_URL str (unset) Override the endpoint for your own node or a paid provider. Unset uses the network's TronGrid default.
TRONGRID_API_KEY str (unset) secret — Required in production once a hot wallet address is set: keyless TronGrid access is throttled unpredictably, and what gets throttled is the check that a withdrawal really happened.
USDT_CONTRACT_ADDRESS str (unset) Must match what the USDt plugin watches. The withdrawal verifier refuses a transfer of any other token. Unset uses the network default; both defaults were read off their chains on 2026-08-11 and answered USDT / 6, so setting this is about agreeing with your plugin rather than about distrusting the default.
TRON_HOT_WALLET_ADDRESS str (unset) The address USDT is sent from. Verification compares every transfer's sender against it, so a wrong value rejects every genuine withdrawal. Unset disables the TRON jobs entirely, which is right for a BTC-only deployment.
TRON_CONFIRMATIONS int 19 ≥ 1 — Blocks before a manual USDT withdrawal is treated as final. 19 is roughly TRON's solidified-block distance.
GAS_MONITOR_INTERVAL_SECONDS float 900.0 > 0 — How often the TRX gas balance of the hot wallet is checked.
TRX_ALERT_THRESHOLD int 200 ≥ 0 — TRX balance below which an alert is raised. A wallet full of USDT and empty of TRX cannot send anything, and the symptom reads as "withdrawals are broken".
USDT_AMOUNT_TOLERANCE_PCT float 1.0 ≥ 0 — How far a settled USDT deposit may deviate from the expected amount before it goes to review instead of crediting. Applies only when the platform supplied an expected amount; 0 disables it.

Alerts

Variable Type Default Notes
NTFY_TOPIC_URL str (unset) Optional ntfy.sh (or compatible) topic URL. Alerts are always logged; this adds a push.
TELEGRAM_BOT_TOKEN str (unset) secret — Optional Telegram bot. Both the token and the chat id must be set or the transport stays off.
TELEGRAM_CHAT_ID str (unset) The chat alerts are sent to. Useless without the bot token.

Outbound webhooks to the platform

Variable Type Default Notes
PLATFORM_WEBHOOK_URL str (unset) Where deposit and withdrawal events are POSTed. Unset disables delivery, and events queue rather than being lost, so turning it on later ships the backlog.
PLATFORM_WEBHOOK_SECRET str (unset) secret — HMAC-SHA256 key for the X-CPA-Signature header. The service refuses to start with a URL set and this unset: an unsigned webhook is one anybody can forge.
OUTBOUND_DELIVERY_INTERVAL_SECONDS float 15.0 > 0 — How often the delivery worker drains the queue. The per-event retry schedule is fixed in code and is not configurable.
OUTBOUND_HTTP_TIMEOUT_SECONDS float 10.0 > 0 — Per-request timeout for one delivery attempt.

Reconciliation and health

Variable Type Default Notes
RECONCILE_INVARIANT_INTERVAL_SECONDS float 3600.0 > 0 — Job C: how often the books are re-derived from the postings and custody is compared against the chain. This is the job that would notice an insolvency, so lengthening it lengthens how long one can go unnoticed.
WORKER_HEARTBEAT_STALE_SECONDS int 300 ≥ 30 — A worker job whose last run is older than this reads as stale in /readyz. /healthz is process-and-database only, so this is how a dead worker is noticed.
WEBHOOK_SIGNATURE_FAILURE_THRESHOLD int 10 ≥ 1 — Failed inbound signature verifications in the last hour before an alert. A spike means a misconfigured secret or somebody probing the endpoint.
CUSTODY_TOLERANCE_UNITS int 0 ≥ 0 — How far custody may sit below user obligations before Job C calls it an insolvency signal, in the asset's smallest unit. 0 is correct and is the default: the expected shortfall is derived from in-flight postings, not from a tuned epsilon.

Rules checked at startup

These are cross-field, so they are not visible in the table above. Each one is a refusal to start, not a warning.

  • DEBUG=true is allowed only with ENVIRONMENT=development. Debug mode leaks internals into responses and logs.
  • DATABASE_URL must be a psycopg 3 PostgreSQL URL — postgresql:// or postgresql+psycopg://.
  • SEED_BTC_WITHDRAWAL_AUTO_LIMIT, SEED_LN_WITHDRAWAL_AUTO_LIMIT and SEED_USDT_WITHDRAWAL_AUTO_LIMIT may not exceed their matching *_DAILY_CAP. Otherwise one withdrawal could clear the 24-hour cap on its own.
  • BITCOIN_NETWORK must be mainnet when ENVIRONMENT=production. Anything else validates withdrawal addresses against the wrong chain.
  • TRONGRID_API_KEY is required in production once TRON_HOT_WALLET_ADDRESS is set. Keyless TronGrid access is throttled unpredictably, and what gets throttled is the check that a withdrawal really happened.
  • USDT_AUTO_WITHDRAW=true is refused outright. The BTCPay USDt plugin has no payout handler, so an auto-approved USDT withdrawal would stall with the user's balance held.
  • PLATFORM_WEBHOOK_SECRET is required once PLATFORM_WEBHOOK_URL is set. An unsigned webhook is one anybody can forge.
  • DEPOSIT_MONITORING_MINUTES may not be below DEPOSIT_INVOICE_EXPIRY_MIN_BTC. BTCPay would stop attributing payments before the invoice had even expired.