Parchment Docs
Deployment

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:

  1. A signed license token (PARCHMENT_LICENSE) — proves the instance is authorized to run billing
  2. 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

  1. Create an organization (or use an existing one)
  2. Create a Premium product with monthly recurring pricing
  3. Note your organization ID (Settings → Organization) and product ID (click the product → URL contains the ID)

2. Generate an API access token

  1. Go to Settings → Developers → Access Tokens
  2. Click New Token
  3. Give it a descriptive name (e.g. "Parchment Server")
  4. Select the following permissions:
    • checkouts:write — create checkout sessions
    • customers:read — look up customers by ID
    • customer_sessions:write — generate customer portal links
    • subscriptions:read — read subscription status
  5. 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.

  1. Go to Settings → Developers → Webhooks
  2. Click Add Endpoint
  3. Set the URL to your server's (SERVER_ORIGIN) webhook path: https://api.your-domain.com/subscriptions/webhook
  4. Under Events, subscribe to:
    • subscription.active
    • subscription.canceled
    • subscription.revoked
  5. Click Create
  6. 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 --keygen

This 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.ts

To set an expiration date:

LICENSE_PRIVATE_KEY=<your-private-key> bun scripts/generate-license.ts --exp 2027-01-01

The output is a PARCHMENT_LICENSE token to set in your environment.

Environment variables

VariableDescriptionRequired
PARCHMENT_LICENSESigned license token (from the generate script)Production only
POLAR_ACCESS_TOKENPolar API access tokenNo
POLAR_WEBHOOK_SECRETPolar webhook signing secretNo
POLAR_ORGANIZATION_IDYour Polar organization IDNo
POLAR_PREMIUM_PRODUCT_IDThe Polar product ID for the Premium tierNo
POLAR_SANDBOXSet to true to use Polar's sandbox environment for testingNo
REGISTRATION_MODEinvite (default) or open — controls whether new users can self-registerNo

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:

EventAction
subscription.activeLinks the Polar customer to the Parchment user, assigns the premium role
subscription.canceledRemoves the premium role
subscription.revokedRemoves 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:

  1. Check the server logs for Valid license detected — billing features available
  2. Visit GET /subscriptions/config — should return { "billingEnabled": true }
  3. The Subscription page should appear in Settings
  4. Free users see an "Upgrade to Premium" button that redirects to Polar checkout
  5. 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/config endpoint returns { "billingEnabled": false }

This is the default for self-hosted instances. No configuration is needed to run in this mode.