Webhooks

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.

About webhooks

Creating webhooks

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) between 6 and 40 characters that will be used to sign each request. You should validate against this secret on each incoming webhook so you can verify that the request came from Lemon Squeezy.
  3. A list of events - This is a list of events that will trigger this webhook.

Webhooks can be set up and managed from Settings > Webhooks in your dashboard or programatically using the Lemon Squeezy API.

Sending custom data

You can pass custom data through the checkout (for example, user identifiers) that can be captured using webhooks.

Read the Taking payments section of our Developer guide for a detailed example.

Viewing webhook events

Recent webhooks sent from your store are logged on the webhooks settings page. You can see which events have been sent, the full payloads of each request and also resend webhooks if you need to.

Testing webhooks

When in test mode, you can set up and test a full webhook integration.

Lemon Squeezy will send webhooks for each event that happens in your test mode store just like it would in live mode.

For more manual testing, you can also simulate webhook events for any test mode subscriptions. Simply choose a subscription event you'd like to receive a webhook for and send it manually to your app from your Lemon Squeezy admin.

Test and live mode webhooks are kept separate within your account, meaning a test mode webhook will only be triggered for test mode data and vice versa.

Tip: To make it easier to check the data that is sent via webhooks, you can use a tool like webhook.site.


Webhook requests

When a webhook event occurs in Lemon Squeezy, a POST request will be sent to the configured webhook URL.

Return a 200 response to show that the webhook was successfully captured. 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. Read on for information about verifying signatures.

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.

If you have added custom data to your checkout, you will find a custom_data object inside the meta field for all Order, Subscription and License key related events.

Example payload

Content-Type: application/json X-Event-Name: order_created X-Signature: {signature} { "meta": { "event_name": "order_created", "custom_data": { "customer_id": 25 } }, "data": { "type": "orders", "id": "1", "attributes": { "store_id": 1, "customer_id": 1, "identifier": "104e18a2-d755-4d4b-80c4-a6c1dcbe1c10", "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, "subtotal_formatted": "$9.99", "discount_total_formatted": "$0.00", "tax_formatted": "$2.00", "total_formatted": "$11.99", "first_order_item": { "id": 1, "order_id": 1, "product_id": 1, "variant_id": 1, "product_name": "Test Limited License for 2 years", "variant_name": "Default", "price": 1199, "created_at": "2021-08-17T09:45:53.000000Z", "updated_at": "2021-08-17T09:45:53.000000Z", "deleted_at": null, "test_mode": false }, "urls": { "receipt": "https://app.lemonsqueezy.com/my-orders/104e18a2-d755-4d4b-80c4-a6c1dcbe1c10?signature=8847fff02e1bfb0c7c43ff1cdf1b1657a8eed2029413692663b86859208c9f42" }, "created_at": "2021-08-17T09:45:53.000000Z", "updated_at": "2021-08-17T09:45:53.000000Z" }, "relationships": { "store": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/store", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/store" } }, "customer": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/customer", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/customer" } }, "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" } }, "discount-redemptions": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/discount-redemptions", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/discount-redemptions" } } }, "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.

Examples

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'] ?? ''; if (!hash_equals($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.'); }

Python (Django) Example

import hashlib import hmac signature = request.META['HTTP_X_SIGNATURE'] secret = '[SIGNING_SECRET]' digest = hmac.new(secret.encode(), request.body, hashlib.sha256).hexdigest() if not hmac.compare_digest(digest, signature): raise Exception('Invalid signature.')

Event types

This is the full list of webhook events available in Lemon Squeezy:

Event nameData sent Description
order_createdOrder objectOccurs when a new order is successfully placed.
order_refundedOrder objectOccurs when a full or partial refund is made on an order.
subscription_createdSubscription objectOccurs when a new subscription is successfully created.
An order_created event will always be sent alongside a subscription_created event.
subscription_updatedSubscription objectOccurs when a subscription's data is changed or updated.
This event can be used as a "catch-all" to make sure you always have access to the latest subscription data.
subscription_cancelledSubscription objectOccurs when a subscription is cancelled manually by the customer or store owner.
The subscription enters a "grace period" until the next billing date, when it will expire. It is possible for the subscription to be resumed during this period.
subscription_resumedSubscription objectOccurs when a subscription is resumed after being previously cancelled.
subscription_expiredSubscription objectOccurs when a subscription has ended after being previously cancelled, or once dunning has been completed for past_due subscriptions. You can manage how long to wait before the system marks deliquent subscriptions as expired. More info
subscription_pausedSubscription objectOccurs when a subscription's payment collection is paused.
subscription_unpausedSubscription objectOccurs when a subscription's payment collection is resumed after being previously paused.
subscription_payment_successSubscription invoice objectOccurs when a subscription payment is successful.
subscription_payment_failedSubscription invoice objectOccurs when a subscription renewal payment fails.
subscription_payment_recoveredSubscription invoice objectOccurs when a subscription has a successful payment after a failed payment.
A subscription_payment_success event will always be sent alongside a subscription_payment_recovered event.
subscription_payment_refundedSubscription invoice objectOccurs when a subscription payment is refunded.
license_key_createdLicense key objectOccurs when a license key is created from a new order.
An order_created event will always be sent alongside a license_key_created event.
license_key_updatedLicense key objectOccurs when a license key is updated.

We suggest subscribing to these events as a minimum so that your application will have the latest data.

If you sell one-off products:

order_created - This event lets you know when new orders are placed.

If you sell subscriptions:

subscription_created - This event lets you know when new subscriptions are created.

subscription_payment_success - This event lets you save billing histories for your subscribing customers.

subscription_updated - This event keeps your application up-to-date with all changes to a subscription.

Events sent during a subscription's lifecycle

Different events will be triggered at various events during a subscription's life.

You can see that subscription_updated is fired at every event after the initial payment. In most cases, subscribing to more granluar events like subscription_resumed or subscription_expired events as well as subscription_updated is not necessary.

Example 1: A typical flow of events

Initial order is placed:

  • order_created
  • subscription_created
  • subscription_payment_success
  • license_key_created (if licenses are enabled on the subscription product)

Successful renewal payment:

  • subscription_payment_success
  • subscription_updated

Payment method updated by customer:

  • subscription_updated

Subscription manually cancelled by the customer or store owner:

  • subscription_cancelled
  • subscription_updated

Subscription expires when the cancelled subscription reaches the ends_at date:

  • subscription_expired
  • subscription_updated

Example 2: With payment failures

Initial order is placed:

  • order_created
  • subscription_created
  • subscription_payment_success
  • license_key_created (if licenses are enabled on the subscription product)

Successful renewal payment:

  • subscription_payment_success
  • subscription_updated

Failed renewal payment (attempt 1):

  • subscription_payment_failed
  • subscription_updated

Successful renewal payment:

  • subscription_payment_success
  • subscription_payment_recovered
  • subscription_updated

Failed renewal payment (attempt 1):

  • subscription_payment_failed
  • subscription_updated

Failed renewal payment (attempt 2):

  • subscription_payment_failed
  • subscription_updated

Failed renewal payment (attempt 3):

  • subscription_payment_failed
  • subscription_updated

Failed renewal payment (attempt 4):

  • subscription_payment_failed
  • subscription_updated

(Subscription enters dunning)

Dunning finishes:

  • subscription_expired
  • subscription_updated

Example payloads

Below are a few examples of webhook event data. You will notice that the data in the payload is one of the objects listed above, plus some meta and the usual relationships and links.

order_created

Returns an order object.

{ "meta": { "event_name": "order_created" }, "data": { "type": "orders", "id": "1", "attributes": { "store_id": 1, "customer_id": 1, "identifier": "89b36d62-4f5c-4353-853f-0c769d0535c8", "order_number": 1, "user_name": "Dan R", "user_email": "[email protected]", "currency": "EUR", "currency_rate": "1.08405", "subtotal": 1499.985, "discount_total": 0, "tax": 359.775, "total": 1859.76, "subtotal_usd": 1626, "discount_total_usd": 0, "tax_usd": 390, "total_usd": 2016, "tax_name": "ALV", "tax_rate": "24.00", "status": "paid", "status_formatted": "Paid", "refunded": null, "refunded_at": null, "subtotal_formatted": "€14.99", "discount_total_formatted": "€0.00", "tax_formatted": "€3.59", "total_formatted": "€18.59", "first_order_item": { "order_id": 1, "product_id": 1, "variant_id": 1, "product_name": "Product One", "variant_name": "Default", "price": 1500, "created_at": "2023-01-17T12:26:23.000000Z", "updated_at": "2023-01-17T12:26:23.000000Z", "test_mode": false }, "urls": { "receipt": "https://app.lemonsqueezy.com/my-orders/89b36d62-4f5c-4353-853f-0c769d0535c8?signature=8847fff02e1bfb0c7c43ff1cdf1b1657a8eed2029413692663b86859208c9f42" }, "created_at": "2023-01-17T12:26:23.000000Z", "updated_at": "2023-01-17T12:26:23.000000Z", "test_mode": false }, "relationships": { "store": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/store", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/store" } }, "customer": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/customer", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/customer" } }, "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" } }, "discount-redemptions": { "links": { "related": "https://api.lemonsqueezy.com/v1/orders/1/discount-redemptions", "self": "https://api.lemonsqueezy.com/v1/orders/1/relationships/discount-redemptions" } } }, "links": { "self": "https://api.lemonsqueezy.com/v1/orders/1" } } }

subscription_created

Returns a subscription object.

{ "meta": { "event_name": "subscription_created" }, "data": { "type": "subscriptions", "id": "1", "attributes": { "store_id": 2, "customer_id": 2, "order_id": 2, "order_item_id": 2, "product_id": 2, "variant_id": 2, "product_name": "Subscription One", "variant_name": "Default", "user_name": "Dan R", "user_email": "[email protected]", "status": "on_trial", "status_formatted": "On_trial", "card_brand": "visa", "card_last_four": "42424", "pause": null, "cancelled": false, "trial_ends_at": "2023-01-24T12:43:48.000000Z", "billing_anchor": 24, "first_subscription_item": { "id": 1, "subscription_id": 1, "price_id": 1, "quantity": 5, "created_at": "2023-01-17T12:43:51.000000Z", "updated_at": "2023-01-17T12:43:51.000000Z" }, "urls": { "update_payment_method": "https://my-store.lemonsqueezy.com/subscription/1/payment-details?expires=1674045831&signature=31cf3c83983a03a6cf92e4ec3b469fc044eace0a13183dcb1d7bc0da3bad6f31", "customer_portal": "https://my-store.lemonsqueezy.com/billing?expires=1674045831&signature=82ae290ceac8edd4190c82825dd73a8743346d894a8ddbc4898b97eb96d105a5" }, "renews_at": "2023-01-24T12:43:48.000000Z", "ends_at": null, "created_at": "2023-01-17T12:43:50.000000Z", "updated_at": "2023-01-17T12:43:51.000000Z", "test_mode": false }, "relationships": { "store": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/store", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/store" } }, "customer": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/customer", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/customer" } }, "order": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/order", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/order" } }, "order-item": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/order-item", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/order-item" } }, "product": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/product", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/product" } }, "variant": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/variant", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/variant" } }, "subscription-items": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/subscription-items", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/subscription-items" } }, "subscription-invoices": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/1/subscription-invoices", "self": "https://api.lemonsqueezy.com/v1/subscriptions/1/relationships/subscription-invoices" } } }, "links": { "self": "https://api.lemonsqueezy.com/v1/subscriptions/1" } } }

subscription_cancelled

Returns a subscription object.

{ "meta": { "event_name": "subscription_cancelled" }, "data": { "type": "subscriptions", "id": "3", "attributes": { "store_id": 2, "customer_id": 2, "order_id": 4, "order_item_id": 4, "product_id": 2, "variant_id": 2, "product_name": "Subscription One", "variant_name": "Default", "user_name": "Dan R", "user_email": "[email protected]", "status": "cancelled", "status_formatted": "Cancelled", "pause": null, "cancelled": true, "trial_ends_at": null, "billing_anchor": 17, "first_subscription_item": { "id": 1, "subscription_id": 1, "price_id": 1, "quantity": 5, "created_at": "2023-01-17T14:15:46.000000Z", "updated_at": "2023-01-17T14:15:46.000000Z" }, "urls": { "update_payment_method": "https://my-store.lemonsqueezy.com/subscription/3/payment-details?expires=1674065845&signature=193397167637b5ae4e3a36741926c4f073cfb3d242831962b32565637db2bbb0", "customer_portal": "https://my-store.lemonsqueezy.com/billing?expires=1674065845&signature=82ae290ceac8edd4190c82825dd73a8743346d894a8ddbc4898b97eb96d105a5" }, "renews_at": "2023-02-17T14:15:43.000000Z", "ends_at": "2023-02-17T14:15:43.000000Z", "created_at": "2023-01-17T14:15:45.000000Z", "updated_at": "2023-01-17T18:17:25.000000Z", "test_mode": false }, "relationships": { "store": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/store", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/store" } }, "customer": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/customer", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/customer" } }, "order": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/order", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/order" } }, "order-item": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/order-item", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/order-item" } }, "product": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/product", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/product" } }, "variant": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/variant", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/variant" } }, "subscription-items": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/subscription-items", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/subscription-items" } }, "subscription-invoices": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/subscription-invoices", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/subscription-invoices" } } }, "links": { "self": "https://api.lemonsqueezy.com/v1/subscriptions/3" } } }

subscription_paused

Returns a subscription object.

{ "meta": { "event_name": "subscription_paused" }, "data": { "type": "subscriptions", "id": "3", "attributes": { "store_id": 2, "customer_id": 2, "order_id": 4, "order_item_id": 4, "product_id": 2, "variant_id": 2, "product_name": "Subscription One", "variant_name": "Default", "user_name": "Dan R", "user_email": "[email protected]", "status": "paused", "status_formatted": "Paused", "pause": { "mode": "void", "resumes_at": null }, "cancelled": false, "trial_ends_at": null, "billing_anchor": 17, "first_subscription_item": { "id": 1, "subscription_id": 1, "price_id": 1, "quantity": 5, "created_at": "2023-01-17T14:15:46.000000Z", "updated_at": "2023-01-17T14:15:46.000000Z" }, "urls": { "update_payment_method": "https://my-store.lemonsqueezy.com/my-orders/91c29979-dade-458e-bd6d-caa895a5d5c3/subscription/3/update-payment-method?expires=1674221847&signature=b56e4fb5e8d102a0d29c3dcca95797bf6bb059d741c0b402f2893969aad53165" }, "renews_at": "2023-02-17T14:15:43.000000Z", "ends_at": null, "created_at": "2023-01-17T14:15:45.000000Z", "updated_at": "2023-01-19T13:37:27.000000Z", "test_mode": false }, "relationships": { "store": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/store", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/store" } }, "customer": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/customer", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/customer" } }, "order": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/order", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/order" } }, "order-item": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/order-item", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/order-item" } }, "product": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/product", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/product" } }, "variant": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/variant", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/variant" } }, "subscription-items": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/subscription-items", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/subscription-items" } }, "subscription-invoices": { "links": { "related": "https://api.lemonsqueezy.com/v1/subscriptions/3/subscription-invoices", "self": "https://api.lemonsqueezy.com/v1/subscriptions/3/relationships/subscription-invoices" } } }, "links": { "self": "https://api.lemonsqueezy.com/v1/subscriptions/3" } } }