# Lemon Squeezy

Connect Lemon Squeezy with an API key, store id and signed webhook so ProductLense records orders, renewals and refunds net of tax.

ProductLense reads your Lemon Squeezy customers, subscriptions, orders and subscription invoices for one store, including refunds. Lemon Squeezy is a merchant of record. ProductLense only reads: it never writes to your Lemon Squeezy store, never refunds, cancels or changes anything.

## What you need

| Field | Secret | Where to find it |
|---|---|---|
| API key | Yes | Settings → API → create API key. |
| Store ID | No | The numeric store id from Settings → Stores. |
| Webhook signing secret | Yes | The signing secret you choose when creating the webhook in Settings → Webhooks. |

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

Lemon Squeezy API keys have no scopes, so the key can read and write; ProductLense only reads. It calls `GET /v1/stores/{id}`, `/v1/customers`, `/v1/subscriptions`, `/v1/orders` and `/v1/subscription-invoices`, filtered to your store.

## Set up

1. In Lemon Squeezy, open Settings → API and create an API key. Note your store id under Settings → Stores.
2. In ProductLense, open your site's Settings → Billing connections → Lemon Squeezy, paste the key and store id, check the Processor fees estimate, and click Connect and import history.
3. Copy the webhook URL from the connection card.
4. In Lemon Squeezy, open Settings → Webhooks → add a webhook with that URL, choose a signing secret and tick the events below.
5. Paste the same signing secret into the connection in ProductLense.

## Webhook

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

Events ProductLense uses:

- `order_created`, `order_refunded`
- `subscription_created`, `subscription_updated`, `subscription_cancelled`, `subscription_expired`, `subscription_resumed`, `subscription_paused`, `subscription_unpaused`
- `subscription_payment_success`, `subscription_payment_refunded`, `subscription_payment_recovered`

Verification: the `X-Signature` header, an HMAC-SHA256 of the raw body with your signing secret. Lemon Squeezy signatures carry no timestamp; replays are rejected by event id.

## What ProductLense reads

- **Customers**: email and name.
- **Subscriptions**: status. The subscription object has no price or interval count, so its amount is recorded as 0 and revenue comes from orders and invoices.
- **Payments**: orders (first purchases) and subscription invoices (renewals; the initial invoice is skipped because the order already counts it).
- **Refunds**: the refunded amount on orders and invoices, with the tax share pro-rated.
- **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

Lemon Squeezy reports tax but not its own platform fee. ProductLense records the tax as the fee, so net revenue is `total − tax`, and a refund returns its pro-rated share of tax.

The connection starts with a fee estimate of 5% + $0.50 per payment, editable under Processor fees on the connection card (percent, fixed amount per payment, and its currency). The estimate is applied only to charges that arrive with a zero fee, which here means orders and invoices with no tax. Charges that carry tax keep the tax as their fee and do not get the platform estimate, so profit on taxed sales is before Lemon Squeezy's platform fee. Estimated fees are flagged, and the dashboard shows the share of fees in a period that is estimated.

> [!IMPORTANT]
> An estimate is only as accurate as the rate you enter. Check your Lemon Squeezy payouts and set the percent and fixed amount to match your actual pricing.

## Match payments to visitors

ProductLense reads the user id from the checkout's custom data, delivered with each webhook: `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. Customers imported by the backfill carry no custom data, so for them ProductLense falls back to the customer email, matched to the `email` trait sent with `identify`, or to a user id that is that email.

```text title="Checkout link with custom data"
https://yourstore.lemonsqueezy.com/checkout/buy/VARIANT_ID?checkout[custom][pl_user_id]=user_123
```

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