Polar setup
Keylight integrates with Polar through Standard Webhooks: every paid order, subscription state change, and successful refund hits Keylight, and Keylight mints, renews, or refunds the matching license. There’s no OAuth — selling only needs the signed webhook and a shared HMAC secret.
Two optional flows do call the Polar API and need an organization token with the checkouts:write scope (step 4): upgrades and Test a purchase.
1. Enable Polar in the dashboard
Section titled “1. Enable Polar in the dashboard”From your dashboard, go to Settings → Integrations → Polar and click Enable Polar. Keylight reveals the webhook URL:
https://api.keylight.dev/webhooks/polar/<your-tenant-id>/livehttps://api.keylight.dev/webhooks/polar/<your-tenant-id>/sandboxCopy it — you’ll paste it into Polar’s webhook settings in step 3.
2. Map the Polar product to a key type
Section titled “2. Map the Polar product to a key type”Mapping lives on the app, not on the integration — one Polar product per key type:
Dashboard → your app → Key types → Pricing → pick the Sandbox or Live tab → the Polar row → Set
Paste the Product ID. In Polar, open the product and use the ⋯ → Copy Product ID menu — it looks like prod_….
The environment tab matters. A product mapped on Live does nothing for a sandbox purchase, and vice versa — sandbox and live keep independent maps, and a Polar sandbox organisation issues entirely different product IDs from your production one.
Unmapped products are rejected with 409 price_unmapped instead of being minted against an unknown app. Polar retries a 409, so adding the missing row fixes the order retroactively.
3. Configure the webhook in Polar
Section titled “3. Configure the webhook in Polar”In the Polar dashboard:
-
Go to Settings → Webhooks → Add Endpoint.
-
Paste the Keylight URL from step 1 into URL.
-
Set Format to Raw.
-
Subscribe to these events:
order.createdrefund.createdsubscription.createdsubscription.updatedsubscription.activesubscription.canceledsubscription.revoked
If you only sell one-time licenses, the two
order/refundevents are enough. -
Set the Secret. Polar lets you type your own or generate a random one — either is fine. Copy it exactly as it appears.
Back in Keylight (Settings → Integrations → Polar), paste the secret into Webhook signing secret and save.
4. Add an API key (optional)
Section titled “4. Add an API key (optional)”Only needed for upgrades and Test a purchase. Selling works on the signing secret alone — skip this otherwise.
In Polar, create an organization token with one scope:
| Group | Scope |
|---|---|
| Checkouts | checkouts:write |
Paste it into Polar API key on the matching slot in Settings → Integrations → Polar. Tokens aren’t interchangeable: one from sandbox.polar.sh works only in Sandbox, one from polar.sh only in Live.
5. Test the connection
Section titled “5. Test the connection”Polar has no “send test event” button, so the way to prove the wiring is a real sandbox purchase — which is also the only thing that exercises the whole path.
- In your sandbox Polar organisation (
sandbox.polar.sh— a separate account and separate product IDs from production), create a checkout link for the product you mapped. Leave its Metadata empty. - Open the link and pay with Stripe’s test card:
4242 4242 4242 4242, any future expiry, any CVC. - Read the result in Polar under Settings → Webhooks → your endpoint → Deliveries, and in Keylight under Dashboard → Licenses.
What the delivery response means:
- A
200 OKfrom Keylight with a fresh license record in Dashboard → Licenses means signature + product map are correct. 400 Invalid request→ the signature didn’t verify. Almost always the secret doesn’t match character-for-character; less often, a proxy in front of Keylight stripped thewebhook-id/webhook-timestampheaders.409 price_unmapped→ the event’s Product ID isn’t in your map (step 2).204 No Content→ Keylight verified the delivery but had nothing to do with it: an event type it doesn’t act on, or anorder.createdwhosestatusisn’t yetpaid.
What happens on a real sale
Section titled “What happens on a real sale”When a customer checks out, Polar fires order.created with data.status === 'paid'. Keylight verifies the Standard Webhooks signature, looks up the app+key-type from data.product_id, mints a license, and records the payment. Subscription renewals arrive as subscription.updated with data.status === 'active' and re-extend the license. subscription.canceled and subscription.revoked flip it to cancelled. A successful refund.created (where data.status === 'succeeded') flips the license to refunded.
Upgrades
Section titled “Upgrades”Upgrades are brokered through Polar’s Checkout API, so they need the API key from step 4 — checkouts:write, on the Live slot.
With that in place, Keylight creates the upgrade checkout itself, stamping the customer’s current license key into the checkout metadata. Polar copies that onto the resulting order, and Keylight links the order to the existing license instead of issuing a second one. The key type’s Upgrade checkout URL is not used for Polar — leave it blank.
Upgrades on subscription key types aren’t supported yet (polar_subscription_upgrade_not_supported) — changing a plan means amending the existing Polar subscription, not paying for a new one-time checkout.
Disconnecting
Section titled “Disconnecting”Settings → Integrations → Polar → Disconnect clears the signing secret and the product map. Existing licenses are untouched — only new webhook deliveries stop minting.