# Polar

Connect Polar with an organization access token and a Standard Webhooks endpoint so ProductLense records orders, refunds and the platform fee.

ProductLense reads your Polar customers, subscriptions and orders, including refunds. Polar is a merchant of record. ProductLense only reads: it never writes to your Polar organization, never refunds, cancels or changes anything.

## What you need

| Field | Secret | Where to find it |
|---|---|---|
| Organization access token | Yes | Settings → Developers → New token. |
| Organization ID | No | The UUID shown in Settings → General. |
| Webhook secret | Yes | The secret generated when you add the webhook endpoint pointing at ProductLense. |

Every plan includes one billing connection per site, Free too; Growth and Scale allow any number. See [plans and limits](/docs/account/plans-and-limits).

## Permissions

Create the organization access token with these scopes, all read-only:

- `customers:read`
- `subscriptions:read`
- `orders:read`
- `organizations:read`

ProductLense calls `GET /v1/organizations/{id}`, `/v1/customers`, `/v1/subscriptions` and `/v1/orders`.

## Set up

1. In Polar, open Settings → Developers → New token and select the four read scopes. Copy your organization id from Settings → General.
2. In ProductLense, open your site's Settings → Billing connections → Polar, paste the token and organization id, and click Connect and import history.
3. Copy the webhook URL from the connection card.
4. In Polar, open Settings → Webhooks → Add endpoint, paste the URL and select the events below.
5. Copy the generated secret into the connection in ProductLense.

## Webhook

URL: `https://api.productlense.app/api/webhooks/polar/<connection-id>` (ProductLense shows the exact URL after you save).

Events ProductLense uses:

- `order.created`, `order.paid`, `order.updated`, `order.refunded`
- `subscription.created`, `subscription.updated`, `subscription.active`, `subscription.canceled`, `subscription.uncanceled`, `subscription.revoked`, `subscription.past_due`
- `customer.created`, `customer.updated`, `customer.state_changed`

Verification: Standard Webhooks headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`). Deliveries older than 5 minutes are rejected.

## What ProductLense reads

- **Customers**: email, name, external id and metadata.
- **Subscriptions**: status, interval and amount.
- **Payments**: orders, at the amount the customer paid including tax.
- **Refunds**: the refunded amount on each order, plus the refunded tax.
- **History**: the last 12 months on first connect, then a nightly reconcile at 03:00 UTC from the last sync minus 48 hours.

## How fees are derived

The fee is the order's `platform_fee_amount` when Polar includes it. Older payloads omit it, and those orders are recorded with a fee of 0.

A fee estimate can be set under Processor fees on the connection card (percent, fixed amount per payment, and its currency). It starts at 0 and is applied only to charges that arrive with a zero fee. Estimated fees are flagged, and the dashboard shows the share of fees in a period that is estimated. An estimate is only as accurate as the rate you enter.

## Match payments to visitors

ProductLense uses the Polar customer's **external id** as the user id. When it is empty, ProductLense reads the customer metadata keys `pl_user_id`, `user_id`, `userId` or `uid`. Use the same id you pass to `identify`. A ProductLense visitor id in `pl_visitor_id` or `visitor_id` is also accepted. Without either, ProductLense falls back to the customer email, matched to the `email` trait sent with `identify`, or to a user id that is that email.

```js title="Polar checkout (Node SDK)" {3}
const checkout = await polar.checkouts.create({
  products: ['PRODUCT_ID'],
  externalCustomerId: user.id,
  customerEmail: user.email,
});
```

See [how payments are attributed](/docs/revenue/attribution).
