Polar
Payment and subscription system with Polar
Beztack includes Polar as one of the supported payment providers, with Better Auth integration and subscription-oriented flows.
For an overview of the provider-agnostic payment system, see Payments Core.
When to use Polar vs Mercado Pago
- Use Polar when your product is centered on Better Auth checkout/portal
workflows and recurring SaaS subscriptions.
- Don't use Polar if you want to sell physical products/services.
- Use Mercado Pago when you need direct Mercado Pago APIs, plan sync, card
processing, and MP-specific events.
- Don't use Mercado Pago if you want to sell worldwide.
See also: Mercado Pago
Setup
Configure the required Polar variables in apps/api/.env:
POLAR_ACCESS_TOKEN=polar_at_xxxxxxxxxxxxx
POLAR_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
POLAR_SERVER=sandbox
POLAR_BASIC_MONTHLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_BASIC_YEARLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_PRO_MONTHLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_PRO_YEARLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_ULTIMATE_MONTHLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_ULTIMATE_YEARLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_SUCCESS_URL=http://localhost:5173/checkout-success
POLAR_CANCEL_URL=http://localhost:5173/pricing
POLAR_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000Implementation references:
- Better Auth + Polar plugin config:
apps/api/server/utils/auth.ts - Unified subscription routes:
apps/api/server/routes/api/subscriptions/*
Core flows
All payment flows use the unified subscription routes, shared across all providers:
1) Products and pricing data
GET /api/subscriptions/products- Returns tier-oriented product data from the active provider plus local DB feature/limit/permission metadata.
2) Checkout
POST /api/subscriptions/checkout- Creates a checkout session for a selected product.
Minimal example:
const response = await fetch("/api/subscriptions/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ productId: "<polar-product-id>" }),
})
const data = await response.json()
window.location.href = data.checkoutUrl3) Subscription management
GET /api/subscriptions— List subscriptions for the current user.GET /api/subscriptions/:id— Get a specific subscription.PATCH /api/subscriptions/:id— Update a subscription (e.g., change plan with proration).DELETE /api/subscriptions/:id— Cancel a subscription.
4) Customer portal
Polar supports a customer portal session via createPortalSession() on the adapter.
5) Usage tracking
Polar usage support is wired through the Better Auth plugin (usage()), so
you can report usage events from your app and enforce limits in product logic.
Webhooks and membership enforcement
Webhook endpoint
POST /api/subscriptions/webhooks- Uses shared webhook validation/processing utilities and updates user/org
subscription state for key events (
order paid,subscription active,subscription canceled,customer state changed).
Server-side access checks
Use membership utilities in protected API handlers, for example via
requireAuth and membership tier checks in apps/api/server/utils/membership.ts.
Common patterns and troubleshooting
Recommended patterns
- Keep product IDs and webhook secrets environment-specific.
- Enforce access on the server (not only in the UI).
- Use webhook handlers as the source of truth for subscription state changes.
- Use
sandboxfirst, then switch toproductiononly when flows are stable.
Quick checks
- Checkout failing: validate
POLAR_ACCESS_TOKEN, product IDs, and payload. - Missing webhook effects: validate public endpoint reachability and
POLAR_WEBHOOK_SECRET. - Incorrect permissions in app: verify membership sync logic and webhook processing logs.