Billing & Subscriptions
Set up Polar-powered subscription billing for your Parchment instance.
Parchment supports a two-tier subscription model (Free and Premium) powered by Polar. Billing is optional — when not configured, all users get every feature for free.
How it works
Premium features are gated by the existing role and permission system. When a user subscribes through Polar, a webhook assigns them the premium role. Cancellation removes it. No separate subscription table is needed.
The billing system requires two things to activate:
- A signed license token (
PARCHMENT_LICENSE) — proves the instance is authorized to run billing - Polar API credentials — connects to your Polar organization for checkout and webhooks
If either is missing, billing is disabled and all features are unlocked for every user (self-hosted mode).
In development (NODE_ENV=development), the license check is skipped. Only the Polar credentials are needed.
Polar setup
Create a Polar organization at polar.sh and configure a product and webhook.
For development and testing, use the Polar Sandbox at sandbox.polar.sh. It mirrors the full Polar environment with test payments — no real charges. Set POLAR_SANDBOX=true in your .env and use credentials from the sandbox dashboard.
1. Create an organization and product
- Create an organization (or use an existing one)
- Create a Premium product with monthly recurring pricing
- Note your organization ID (Settings → Organization) and product ID (click the product → URL contains the ID)
2. Generate an API access token
- Go to Settings → Developers → Access Tokens
- Click New Token
- Give it a descriptive name (e.g. "Parchment Server")
- Select the following permissions:
checkouts:write— create checkout sessionscustomers:read— look up customers by IDcustomer_sessions:write— generate customer portal linkssubscriptions:read— read subscription status
- Copy the token — this is your
POLAR_ACCESS_TOKEN
3. Configure the webhook
Polar uses webhooks to notify your server when a subscription changes. The endpoint must be publicly reachable.
- Go to Settings → Developers → Webhooks
- Click Add Endpoint
- Set the URL to your server's (
SERVER_ORIGIN) webhook path:https://api.your-domain.com/subscriptions/webhook - Under Events, subscribe to:
subscription.activesubscription.canceledsubscription.revoked
- Click Create
- Copy the Signing Secret — this is your
POLAR_WEBHOOK_SECRET
For local development, your webhook URL must be reachable from the internet. Use a tunnel like ngrok or Cloudflare Tunnel: ngrok http 5000, then use the generated URL as the webhook endpoint (e.g. https://abc123.ngrok.io/subscriptions/webhook).
License key
Billing requires a cryptographically signed license token. This prevents unauthorized instances from enabling the billing system.
Generate a keypair (one-time)
cd server
bun scripts/generate-license.ts --keygenThis outputs a private key and a public key. Keep the private key secret. The public key is embedded in server/src/lib/license.ts — replace the placeholder with your real public key.
Issue a license
LICENSE_PRIVATE_KEY=<your-private-key> bun scripts/generate-license.tsTo set an expiration date:
LICENSE_PRIVATE_KEY=<your-private-key> bun scripts/generate-license.ts --exp 2027-01-01The output is a PARCHMENT_LICENSE token to set in your environment.
Environment variables
| Variable | Description | Required |
|---|---|---|
PARCHMENT_LICENSE | Signed license token (from the generate script) | Production only |
POLAR_ACCESS_TOKEN | Polar API access token | No |
POLAR_WEBHOOK_SECRET | Polar webhook signing secret | No |
POLAR_ORGANIZATION_ID | Your Polar organization ID | No |
POLAR_PREMIUM_PRODUCT_ID | The Polar product ID for the Premium tier | No |
POLAR_SANDBOX | Set to true to use Polar's sandbox environment for testing | No |
REGISTRATION_MODE | invite (default) or open — controls whether new users can self-register | No |
All Polar variables are optional — when omitted, billing is disabled and all features are unlocked (self-hosted mode). The license token is only required in production; in development (NODE_ENV=development) the license check is skipped.
Add these to your .env file alongside the existing configuration.
Webhook events
The server handles these Polar webhook events:
| Event | Action |
|---|---|
subscription.active | Links the Polar customer to the Parchment user, assigns the premium role |
subscription.canceled | Removes the premium role |
subscription.revoked | Removes the premium role |
All other events are acknowledged with a 200 response and ignored.
Verifying the setup
After configuring all variables and restarting the server:
- Check the server logs for
Valid license detected — billing features available - Visit
GET /subscriptions/config— should return{ "billingEnabled": true } - The Subscription page should appear in Settings
- Free users see an "Upgrade to Premium" button that redirects to Polar checkout
- After checkout, the Polar webhook fires and the user gains premium features
Self-hosted mode
When billing is not configured, Parchment runs in self-hosted mode:
- All users receive every permission (including premium features)
- The Subscription settings page is hidden
- Polar SDK code is never loaded
- The
/subscriptions/configendpoint returns{ "billingEnabled": false }
This is the default for self-hosted instances. No configuration is needed to run in this mode.