Revenue
Connect Lemon Squeezy
Lemon Squeezy sends orders and subscription payments to a webhook signed with a secret MetricRaven generates. Checkout custom data links each order to the visit that started it.
Set up the webhook
Open Settings → Integrations → Lemon Squeezy
Choose Set up Lemon Squeezy. Copy the webhook URL and the signing secret; the secret is shown once.
Add a webhook in Lemon Squeezy
In Lemon Squeezy, open Settings → Webhooks and choose +. Paste the URL as the callback URL and the secret as the signing secret.
Select the payment events
Select
order_created,order_refunded,subscription_payment_successandsubscription_payment_refunded. For subscription goals (trial started, cancelled, ended), also selectsubscription_created,subscription_updated,subscription_cancelled,subscription_resumed,subscription_expired,subscription_pausedandsubscription_unpaused. Then save.
Pass the visitor ID at checkout
Lemon Squeezy returns checkout custom data in each webhook's meta.custom_data. Use these keys:
| Key | Value |
|---|---|
| metricraven_visitor_id | The visitor ID from getVisitorId() (24 or 32 hex characters). |
| metricraven_user_id | Optional. Your signed-in user's ID, set on the server, matching the Identify API. |
| metricraven_plan | Optional. A plan name for reports; otherwise the product from the order's first item is used. |
Read the visitor ID in the browser
The MetricRaven script exposes
window.analytics.getVisitorId(). Read it when checkout starts and send it to your checkout code:script.js// Read this in the visitor's browser when they start checkout. const visitorId = window.analytics?.getVisitorId() ?? null; // Send visitorId to your own checkout endpoint alongside its usual request data. // Use the authenticated user ID from your backend; do not trust a browser-supplied user ID.Add it to the checkout
For a checkout link or Lemon.js overlay, add a query parameter:
Checkout link// Browser: add the visitor ID to a Lemon Squeezy checkout link. const url = new URL("https://YOUR_STORE.lemonsqueezy.com/buy/YOUR_VARIANT_ID"); const visitorId = window.analytics?.getVisitorId(); if (visitorId) url.searchParams.set("checkout[custom][metricraven_visitor_id]", visitorId); window.location.href = url.toString();When you create checkouts on your server, put it in
checkout_data.custom:Checkouts API// Server: create a checkout with the Lemon Squeezy API. // visitorId came from your browser; user is your authenticated user (or null for guests). const custom = { ...(visitorId ? { metricraven_visitor_id: visitorId } : {}), ...(user ? { metricraven_user_id: user.id } : {}), }; const response = await fetch("https://api.lemonsqueezy.com/v1/checkouts", { method: "POST", headers: { Accept: "application/vnd.api+json", "Content-Type": "application/vnd.api+json", Authorization: `Bearer ${process.env.LEMONSQUEEZY_API_KEY}`, }, body: JSON.stringify({ data: { type: "checkouts", attributes: { checkout_data: { email: user?.email, custom } }, relationships: { store: { data: { type: "stores", id: "YOUR_STORE_ID" } }, variant: { data: { type: "variants", id: "YOUR_VARIANT_ID" } }, }, }, }), }); const { data } = await response.json(); // Redirect the visitor to data.attributes.url
MetricRaven links each payment to a visit using the first of these that is available:
- The
metricraven_visitor_idpassed through checkout. - The
metricraven_user_id(or the provider's customer external ID), matched to a visitor you linked with the Identify API. - An earlier payment from the same provider customer, so renewals keep the original attribution.
- The checkout email, matched to an email you sent to the Identify API.
- The checkout email, matched to a browser that called payment({ email }) within an hour of the payment.
Can't set custom data on your checkout? See Other Lemon Squeezy methods to match by the buyer's email.
Verify it works
After a delivery, the Lemon Squeezy card in Settings → Integrations shows Connected, the number of verified events and the last event type. A live payment appears in Revenue in its own currency; with a visitor ID it is credited to that visit's source in Traffic.
If the card says a delivery was rejected, the signing secret doesn't match. Paste it again or generate a replacement.
Details
- The first payment of a subscription is counted from its order. Later
subscription_payment_successinvoices withbilling_reason: renewalare added as renewals and keep the customer's attribution. - Amounts are converted from cents using each currency's decimals;
totalincludes tax. Refunds use Lemon Squeezy's cumulativerefunded_amount. - Test mode orders are acknowledged but not counted.
- Signature:
X-Signatureis the hex HMAC-SHA256 of the raw request body with your signing secret. - Each delivery is verified with an HMAC signature over the exact request body, compared in constant time. Unsigned or altered requests are rejected with
401and never change revenue. - Redelivered events are recognized and ignored, so provider retries never add a payment twice. Refunds reduce the original payment and never exceed it.
- The signing secret is stored encrypted, bound to this site, and never shown in reports, share links or logs. Disconnecting deletes it; imported revenue stays in your reports.
- Free trials and zero-amount orders are not revenue. A subscription cancellation is not a refund: revenue already paid stays.
- Webhooks deliver new events only. Payments from before you connected are not imported, and live MRR is currently available for Stripe only.
- Use one revenue path per purchase: don't also send the same orders through the Payments API.