Lemon Squeezy uses webhooks to notify your application when an event happens in your store. Webhooks are particularly useful for asynchronous events like when a license key is created or when a subscription expires.

Webhooks can be set up in your store settings panel. Webhooks require three elements to function:

  1. A callback URL - This is the URL that Lemon Squeezy will send a POST request to when an event is triggered.
  2. A signing secret - This is a secret (usually a random string) that will be used to sign each request so that your application can validate that the request came from Lemon Squeezy.
  3. A list of events - This is a list of events that will trigger this webhook.

Lemon Squeezy supports passing custom data through the checkout that can be captured using webhooks. See this doc for more information.

Note that webhooks are live and test mode specific. A test mode webhook will only be triggered for test mode data and vice versa.

Webhook requests

When a webhook event occurs in Lemon Squeezy, a POST request will be sent to the configured webhook URL. If the response status is 200 then the webhook will consider the request successfully sent. If the response status is anything else, the webhook will be retried up to three more times using an exponential backoff strategy (e.g. 5 secs, 25 secs, 125 secs) at which point the request will be considered failed and will no longer be retried.

The webhook body will be a JSON:API resource object that relates to the event (e.g. “order” events will be sent an order object, “subscription” events will be sent a subscription object, etc). The request headers will include:

  • A Content-Type header set to application/json.
  • An X-Event-Name header with the name of the event that triggered the webhook.
  • An X-Signature header containing the request signature.

If a webhook is configured to send a request to an HTTPS URL, the SSL certificate of the URL will be verified before sending the request.

Example payload

{ "meta": { "event_name": "order_created" }, "data": { "type": "orders", "id": "1", "attributes": { "store_id": 1, "identifier": "636f855c-1fb9-4c07-b75c-3a10afef010a", "order_number": 1, "user_name": "Darlene Daugherty", "user_email": "[email protected]", "currency": "USD", "currency_rate": "1.0000", "subtotal": 999, "discount_total": 0, "tax": 200, "total": 1199, "subtotal_usd": 999, "discount_total_usd": 0, "tax_usd": 200, "total_usd": 1199, "tax_name": "VAT", "tax_rate": "20.00", "status": "paid", "status_formatted": "Paid", "refunded": false, "refunded_at": null, "created_at": "2021-08-11T13:47:29.000000Z", "updated_at": "2021-08-11T13:54:54.000000Z" }, "relationships": { "store": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/store", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/store" } }, "order-items": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/order-items", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/order-items" } }, "subscriptions": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/subscriptions", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/subscriptions" } }, "license-keys": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/license-keys", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/license-keys" } } }, "links": { "self": "https://api.lemonsqueezy.com/v1/orders/1" } } }

Signing requests

To ensure that webhook requests are coming from Lemon Squeezy, you will be asked to enter a signing secret when creating your webhook. The secret can be anything you want but is normally a random string between 6 and 40 characters in length.

When the webhook request is sent, Lemon Squeezy will use the signing secret to generate a hash of the payload and send the hash in the X-Signature header of the request. You can use the same secret to calculate the hash in your application and check it against the request signature to verify that the hashes match.


Lemon Squeezy uses a HMAC hex digest to compute the hash. Calculating the hash is different in each programming language.

PHP Example

$secret = '[SIGNING_SECRET]'; $payload = file_get_contents('php://input'); $hash = hash_hmac('sha256', $payload, $secret); $signature = $_SERVER['HTTP_X_SIGNATURE'] ?? null; if ($hash !== $signature) { throw new Exception('Invalid signature.'); }

Node.js Example

const crypto = require('crypto'); const secret = '[SIGNING_SECRET]'; const hmac = crypto.createHmac('sha256', secret); const digest = Buffer.from(hmac.update(request.rawBody).digest('hex'), 'utf8'); const signature = Buffer.from(request.get('X-Signature') || '', 'utf8'); if (!crypto.timingSafeEqual(digest, signature)) { throw new Error('Invalid signature.'); }