Configuration
keys and connections.
Start-up settings live in .env; everything else is set in the Admin console. This page lists every variable, then connects Stripe and Telegram step by step.
01Two places, one rule
| Where | What goes there | A change needs |
|---|---|---|
.env | Address, port, database path, operator claim, proxy trust, the console key; optionally the Stripe and Telegram secrets | A restart (sudo systemctl restart receipts) |
Admin console (/admin) | Fees, payment methods, branding, publishing limits, measurement, delivery, pages; Stripe and Telegram secrets under Integrations | Save. Stripe and Telegram secrets need a restart. |
A secret set in .env wins and locks the field in the console. Otherwise the value saved in the console (encrypted with ADMIN_SECRET_KEY) applies. Nothing has a working default key: you bring every credential.
02Every variable
| Variable | Default if empty | Purpose |
|---|---|---|
PUBLIC_URL Required | http://localhost:8787 | The exact public address, with https://. Used in receipts, Telegram messages, Stripe return links and the provider webhook address. With https:// session cookies are marked Secure. |
PORT | 8787 | Local port the app listens on. Your proxy forwards to it. |
DATABASE_PATH | data/receipts.db | The SQLite database. The folder is created if missing. The console keeps admin.sqlite in the same folder. |
ADMIN_EMAIL | (setup code) | The only email that can create the operator account on a fresh install. Empty = the one-time setup code from the log. |
ADMIN_SECRET_KEY Recommended | generated into data/.admin-secret-key | 32 random bytes (openssl rand -hex 32) that encrypt the secrets saved in the console. Keep it out of git and back it up with data/admin.sqlite. |
TRUST_PROXY | 0 | 1 reads the visitor address from X-Forwarded-For for sign-in limits. Only behind your own proxy (Caddy, nginx, Cloudflare). |
STRIPE_SECRET_KEY | (off) | Card payments. Or set it in the console → Integrations. Stripe setup. |
STRIPE_WEBHOOK_SECRET | (off) | Signing secret of your webhook endpoint. Without it /webhooks/stripe answers 503. |
TELEGRAM_BOT_TOKEN | (off) | Your own bot. Or set it in the console → Integrations. Telegram setup. |
OTS_CALENDARS | public calendars | Comma-separated OpenTimestamps calendar URLs. Empty uses a.pool.opentimestamps.org, b.pool.opentimestamps.org and a.pool.eternitywall.com. |
DEMO_MODE | 0 | 1 = a labelled demo checkout (no money moves), one-click demo logins and labelled strategy bots on live prices. Never in production. |
DEMO_PUBLIC | 0 | With DEMO_MODE=1: a demo reachable from the internet. The console is read-only for visitors and free-text fields that would be public or sealed forever are locked. Not listed in .env.example. |
WORKERS | 1 | 0 turns off background jobs: pricing (every minute), anchoring, Bitcoin confirmation checks (every 15 minutes), access expiry (every 5 minutes), performance billing (hourly). Keep 1. |
Source: .env.example, src/config.ts, src/admin/kit.ts.
03Stripe, step by step
Receipts talks to Stripe over its REST API with your own keys. Subscriptions, Provider Pro and verification fees are paid through Stripe Checkout into your Stripe account, and the app records each payment and splits it. Do the whole setup in test mode first (sk_test_…).
- Secret keyStripe Dashboard → Developers → API keys → into
STRIPE_SECRET_KEY(or Admin → Integrations). - Webhook endpointDevelopers → Webhooks → add
https://signals.example.com/webhooks/stripe. - EventsSelect
invoice.paid,invoice.payment_failed,customer.subscription.deleted,checkout.session.completed,charge.refunded,charge.dispute.created,charge.dispute.closed. - Signing secretCopy the endpoint's
whsec_…intoSTRIPE_WEBHOOK_SECRET, then restart. The start-up line saysStripe on. - TestSubscribe from a second account with card
4242 4242 4242 4242. Afterinvoice.paidthe subscription shows in Account and in Admin → Subscriptions. - Go liveWith the live key and a live endpoint; its signing secret is different.
# testing on your own computer
stripe login
stripe listen --forward-to localhost:8787/webhooks/stripe
# put the printed whsec_... in STRIPE_WEBHOOK_SECRET and restart
- Cancelling keeps access until the end of the paid period. A live renewal gets a 48-hour grace after the period ends.
- A provider's price change applies to new checkouts; existing subscribers keep their price.
- Refunds and lost disputes take the provider's share back out of their balance in the original split; a won dispute puts it back.
- Coupons work with bank and crypto payments, not with card payments.
Webhook signatures, refunds and payment recording are covered by automated tests with synthetic events. A real Stripe account was not used.
04Telegram, step by step
- Create the botIn Telegram open @BotFather, send
/newbot, choose a name and a username ending inbot. - Add the token
TELEGRAM_BOT_TOKEN=123456789:your-tokenin.env(or Admin → Integrations), then restart. The start-up line saysTelegram on. The bot uses long polling: no webhook or open port needed. - Members link accountsSubscriptions and payments (
/account) → Connect Telegram shows a one-timet.me/<bot>?start=<code>link (15 minutes). Pressing Start answers "Linked." One Telegram account per site account. - Providers connect a VIP groupThey add the bot as an administrator with Invite users via link and Ban users, then send
/connectin the group (needs Provider Pro when you charge for it).
When a subscription becomes active the bot DMs the subscriber a personal join-request link (valid 7 days) and approves it only from that member's linked account. When access ends it removes them. Admin → Delivery switches DMs and group posts on or off and sets a footer.
The command parser and handlers are covered by tests; a live bot was not used while writing these docs.