Skip to main content

Overview

Featul integrates with Polar for subscription billing and payment processing. Polar provides:
  • Subscription management
  • Checkout flows
  • Customer portal
  • Usage-based billing
  • Webhook handling
The integration uses the @polar-sh/better-auth plugin for seamless authentication integration.

Polar Setup

1. Create Polar Account

  1. Sign up at Polar
  2. Create an organization
  3. Choose between sandbox (testing) or production mode

2. Create Products

Featul supports multiple subscription tiers:
  1. Go to Polar Dashboard > Products
  2. Create subscription products:
    • Starter Monthly
    • Starter Yearly
    • Professional Monthly
    • Professional Yearly
  3. Note the Product IDs for each subscription
You can create custom pricing tiers based on your needs. The product names are flexible.

3. Generate Access Token

  1. Go to Polar Dashboard > Settings > API
  2. Create a new API token with permissions:
    • Read customers
    • Write customers
    • Read subscriptions
    • Write subscriptions
  3. Copy the access token

4. Configure Webhook

  1. Go to Polar Dashboard > Settings > Webhooks
  2. Add a new webhook endpoint:
    • URL: https://yourdomain.com/api/auth/polar/webhook
    • Events: Select all subscription events
  3. Copy the webhook secret
Keep your webhook secret secure. It’s used to verify webhook authenticity.

Environment Variables

Add these to your .env file:
.env
POLAR_ACCESS_TOKEN=polar_pat_your_access_token
POLAR_WEBHOOK_SECRET=whsec_your_webhook_secret
POLAR_SERVER=sandbox
POLAR_PRODUCT_ID_STARTER_MONTHLY=prod_xxxxxx
POLAR_PRODUCT_ID_STARTER_YEARLY=prod_yyyyyy
POLAR_PRODUCT_ID_PROFESSIONAL_MONTHLY=prod_zzzzzz
POLAR_PRODUCT_ID_PROFESSIONAL_YEARLY=prod_wwwwww

Variable Details

  • POLAR_ACCESS_TOKEN: Your Polar API access token
  • POLAR_WEBHOOK_SECRET: Webhook signing secret
  • POLAR_SERVER: Either sandbox or production
  • POLAR_PRODUCT_ID_*: Product IDs for each subscription tier
Configuration in code: packages/auth/src/auth.ts:64-90
For backward compatibility, you can use legacy environment variables POLAR_PRODUCT_ID_MONTHLY and POLAR_PRODUCT_ID_YEARLY which will be treated as Starter plan products.

Polar Plugin Configuration

The Polar plugin is configured with:
polar({
  client: polarClient,
  createCustomerOnSignUp: true,
  use: [
    checkout({
      products: polarCheckoutProducts,
      successUrl: "/start?checkout_id={CHECKOUT_ID}",
      authenticatedUsersOnly: true,
    }),
    portal(),
    usage(),
    webhooks({
      secret: polarWebhookSecret,
      onSubscriptionCreated: async (payload) => {
        await syncPolarSubscription(payload.data)
      },
      onSubscriptionUpdated: async (payload) => {
        await syncPolarSubscription(payload.data)
      },
      // ... other webhook handlers
    }),
  ],
})
Configuration in code: packages/auth/src/auth.ts:136-150

Checkout Flow

The checkout plugin configuration:
  • Maps product IDs to slugs (starter-monthly, starter-yearly, etc.)
  • Redirects to /start?checkout_id={CHECKOUT_ID} after successful checkout
  • Requires authentication before checkout
Code reference: packages/auth/src/auth.ts:77-105 To create a checkout session in your app:
// This is handled by the Polar better-auth plugin
// Users can access checkout at: /api/auth/polar/checkout?product=starter-monthly

Customer Portal

The portal plugin provides a customer self-service portal where users can:
  • View subscription details
  • Update payment methods
  • Cancel or modify subscriptions
  • View invoices
Access the portal at: /api/auth/polar/portal Code reference: packages/auth/src/auth.ts:106

Webhook Events

Featul handles these Polar webhook events:
  • subscription.created: New subscription created
  • subscription.updated: Subscription details changed
  • subscription.active: Subscription became active
  • subscription.canceled: Subscription was canceled
  • subscription.uncanceled: Canceled subscription was reactivated
  • subscription.revoked: Subscription was revoked
All events trigger the syncPolarSubscription function to update the local database. Code reference: packages/auth/src/auth.ts:110-134
Webhook handling requires both POLAR_ACCESS_TOKEN and POLAR_WEBHOOK_SECRET to be configured.

Subscription Sync

The syncPolarSubscription function:
  1. Receives webhook payload from Polar
  2. Extracts subscription and customer data
  3. Updates user subscription status in the database
  4. Manages subscription lifecycle
Implementation: packages/auth/src/polar.ts

Usage-Based Billing

The usage plugin enables tracking feature usage for billing:
usage()
Code reference: packages/auth/src/auth.ts:107 This allows you to:
  • Report usage metrics to Polar
  • Implement metered billing
  • Track feature limits per plan

Customer Creation

When enabled, the Polar plugin automatically creates a Polar customer record when users sign up:
createCustomerOnSignUp: true
Code reference: packages/auth/src/auth.ts:138 This links authentication with billing from the start.

Sandbox vs Production

Use sandbox mode for testing:
.env
POLAR_SERVER=sandbox
Use production mode for live payments:
.env
POLAR_SERVER=production
Code reference: packages/auth/src/auth.ts:66-67
Always test your billing integration thoroughly in sandbox mode before switching to production.

Dependencies

Polar integration requires these packages:
"@polar-sh/better-auth": "^x.x.x",
"@polar-sh/sdk": "^x.x.x"
These are included in the @featul/auth package.

Disabling Billing

If you don’t need billing functionality:
  1. Remove or leave empty the Polar environment variables
  2. The Polar plugin will be automatically disabled
const polarPlugin = polarClient
  ? polar({ /* config */ })
  : null

// ...

plugins: [
  // ...
  ...(polarPlugin ? [polarPlugin] : []),
]
Code reference: packages/auth/src/auth.ts:136-150,248
Without Polar configuration, billing features will be disabled but the rest of the application will function normally.

Testing Webhooks Locally

To test webhooks during development:
  1. Use a tool like ngrok to expose your local server:
    ngrok http 3000
    
  2. Update your Polar webhook URL to the ngrok URL:
    https://your-ngrok-url.ngrok.io/api/auth/polar/webhook
    
  3. Test subscription flows and watch webhook events in real-time

Alternative Payment Providers

To use a different payment provider (Stripe, Paddle, etc.):
  1. Remove the Polar plugin from packages/auth/src/auth.ts
  2. Implement your own billing integration
  3. Create webhook handlers for subscription events
  4. Update the database schema if needed to store subscription data
The better-auth plugin architecture makes it easy to swap payment providers while maintaining authentication integration.

Build docs developers (and LLMs) love