# Connect Lemon Squeezy

Create licenses automatically from Lemon Squeezy orders and subscriptions, with offline certificates.

Lemon Squeezy can issue its own license keys, validated online through its API. Connecting it to Keyrail instead gives you signed certificates verified offline, per-product machine fingerprints, the same SDKs and API as with Paddle, and a single place for all your licenses.

## 1. Create the integration

In the dashboard, open **Integrations** in the sidebar and click **Connect provider**. Choose **Lemon Squeezy**, the product, and the **Environment** (**Sandbox (test payments)** for test mode, **Live** for real sales), then **Connect**. The integration shows its **Webhook URL**; once you have created the webhook in Lemon Squeezy (step 2), paste the same signing secret in **Webhook secret** and click **Save secret**, then map each variant in the **Price map** (**Add price**, then **Save price map**). See the [dashboard guide](/docs/dashboard/#integrations). Or with the [vendor API](/docs/vendor-api/#integrations), where you can send the secret right away:

```bash
curl https://api.keyrail.tech/v1/integrations \
  -H "Authorization: Bearer $KEYRAIL_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "lemonsqueezy",
    "product": "prd_7k2m9q4x1c8d3f6h0j5n",
    "priceMap": { "412345": "pro", "412346": "lifetime" },
    "webhookSecret": "a-long-random-string-you-choose"
  }'
```

`priceMap` keys are Lemon Squeezy **variant IDs**, values are plan codes. The response contains the `webhookUrl`: `https://api.keyrail.tech/v1/webhooks/lemonsqueezy/int_…`.

## 2. Add the webhook in Lemon Squeezy

In Lemon Squeezy, **Settings → Webhooks → +**:

- **Callback URL**: the `webhookUrl`.
- **Signing secret**: the same `webhookSecret` you sent to Keyrail.
- **Events**: `order_created`, `order_refunded`, `subscription_created`, `subscription_updated`, `subscription_payment_success`.

## 3. Test

Enable test mode in Lemon Squeezy and buy with a test card. The license appears in **Licenses**, the webhook in **Events**, and the customer receives the key. Nothing happened? See [no license after a payment](/docs/troubleshooting/#no-license-after-a-payment).

Test-mode events (`meta.test_mode: true`) are only processed by an integration whose `environment` is `sandbox`; a `live` integration acknowledges them and ignores them (the audit log says `test mode event on a live integration`), so test orders never create real licenses. Likewise a sandbox integration ignores live events. Create one integration per environment, each with the signing secret of the matching webhook.

## What each event does

| Lemon Squeezy event | Effect on the license |
|---|---|
| `order_created` with a non-subscription variant | A `perpetual` license. |
| `subscription_created`, `subscription_payment_success` | `paidUntil` = `renews_at`; status `active`. |
| `subscription_updated` | Status mapped from the subscription status (below). |
| `order_refunded` | Status `refunded`. |

| Lemon Squeezy subscription status | License status |
|---|---|
| `active`, `on_trial` | `active` |
| `past_due`, `unpaid` | `past_due` |
| `cancelled` | `canceled` |
| `expired` | `expired` |
| `paused` | `suspended` |

The customer is matched by email, or created. One customer with one subscription to a plan has one license, reused if they subscribe again later.

## Signature verification

The `X-Signature` header must be the hex HMAC-SHA256 of the raw request body with your signing secret. Otherwise Keyrail answers `401`. See [webhooks](/docs/webhooks/).
