Overview
aSaaSin integrates Polar for subscriptions and one‑time purchases. You’ll set environment variables, create products, map them to internal plans, and enable a webhook that keeps the database in sync.
Add to env
# Feature flag
ENABLE_POLAR=true
# Polar
POLAR_API_KEY=
POLAR_WEBHOOK_SECRET=
POLAR_SUCCESS_URL=https://your-domain/success?checkout_id={CHECKOUT_ID}
Save the file, then restart the dev server if it was running.
What each key is for
ENABLE_POLAR=true
- Controls whether payment and subscription functionality is active. When false, payment operations are skipped safely without errors.POLAR_API_KEY
- API key from Polar dashboard for authenticating with Polar's API.POLAR_WEBHOOK_SECRET
- Secret for validating webhook signatures from Polar events.POLAR_SUCCESS_URL
- Redirect URL after successful checkout completion, must include{CHECKOUT_ID}
placeholder for order tracking.
Create products
- In Polar, create products for one‑time purchases and/or subscription plans.
- Copy each product’s ID.
- Add these IDs to your DB table mapping.
Map products to plans
Plans live in subscription_plans
with (name, billing_cycle)
unique. Store Polar’s product_id
per plan and capability flags in features
JSONB (e.g., maxProjects
, maxApiTokens
). The webhook uses product_id
to resolve the internal plan.
Subscription flow
- User clicks Subscribe on Pricing.
- If logged out, send them to Sign up with:
product_id=<polar-product>
redirect_to=/dashboard
- After auth, redirect to Dashboard with that
product_id
. SubscribeRedirector
reads the URL, calls a server action to create Polar checkout, then redirects the user to Polar.
One‑time purchase flow
- User clicks Buy on Pricing.
- The button calls a server action (e.g.,
startCheckoutAction
) withproduct_id
. - The action creates a Polar checkout and returns a
redirectUrl
. - The client redirects to Polar (no authentication required).
- After payment, the user is redirected to
POLAR_SUCCESS_URL
.
// Server action
export async function startCheckoutAction(formData: FormData) {
const productId = formData.get('product_id')?.toString();
if (!productId) return { success: false, error: 'Missing product ID.' };
const redirectUrl = await createPolarCheckout({ productId });
return { success: true, data: { redirectUrl } };
}
Webhook configuration
- Endpoint URL (local or prod):
/api/webhooks/polar
- Secret: set
POLAR_WEBHOOK_SECRET
in.env
and verify signatures in the handler. - Events to enable:
subscription.created
subscription.updated
subscription.canceled
order.created
order.refunded
What the handler does
- Verify the signature and parse the event.
- On
subscription.created
/subscription.updated
: look up the user by e‑mail, mapproduct_id
→subscription_plans.id
, then upsertsubscriptions
with:subscription_plan_id
,subscription_id
,customer_id
,status
,current_period_start
,current_period_end
,canceled_at
. - On
subscription.canceled
: set status tocanceled
and recordcanceled_at
. - On
order.*
: log or trigger fulfillment for one‑time purchases as needed.
Security & reliability
- Use the Supabase service role only on the server.
- Make writes idempotent (unique constraints + upserts) and return
200
even on duplicate deliveries you safely ignore.
Local development
Start a tunnel and wire it into your env and Polar Sandbox.
# Run your app
yarn dev
# Expose port 3000
ngrok http 3000
Use the generated ngrok URL in two places:
.env
:POLAR_SUCCESS_URL=https://<ngrok-id>.ngrok-free.app/success?checkout_id={CHECKOUT_ID}
- Polar Sandbox → Settings → Webhooks:
https://<ngrok-id>.ngrok-free.app/api/webhooks/polar
Enable: order.created
, order.refunded
, subscription.created
, subscription.canceled
.
Production webhook
Configure the same events for your live endpoint (e.g., https://demo.asaasin.dev/api/webhooks/polar
) and set a production POLAR_SUCCESS_URL
pointing to your success page with the checkout_id
placeholder.