Developer guide

Staying in sync with webhooks

Webhooks allow Lemon Squeezy to send new data to your application when certain events occur inside your store.

For example, if a subscription renewal is charged, data about the payment can be sent automatically to an endpoint in your app. You can then consume and process this data and update your application so that your users can see this renewal (and, crucially, retain access to your product).

Webhook events

These are the different events for which webhooks are sent:

  • order_created
  • order_refunded
  • subscription_created
  • subscription_updated
  • subscription_cancelled
  • subscription_resumed
  • subscription_expired
  • subscription_paused
  • subscription_unpaused
  • subscription_payment_failed
  • subscription_payment_success
  • subscription_payment_recovered
  • license_key_created
  • license_key_updated

To find out what these events reflect in your Lemon Squeezy account, find the detailed run-down in the Webhooks documentation.

Example webhook data

Webhook events are sent as JSON via POST requests to your endpoint.

The data that is available in each webhook request is either a Subscription, Subscription invoice, Order or License key object. Each of our webhooks follows the same structure as below.

An example subscription_created event’s request looks like this:

{ "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" } } }

You can see the data object here is a Subscription object.

If you added custom data to the checkout, it will be available in meta.custom_data:

{ "meta": { "event_name": "subscription_created", "custom_data": { "user_id": 123 } }, "data": { "type": "subscriptions", ... } }

When you receive webhooks events to your endpoint, you should update any local data in your app, like an order ID, renewal date, status or update payment method URL.

Alongside the data are relationships to related objects inside Lemon Squeezy. You can use the provided URLs to make sequential GET requests to access, for example, a product variant object using the URL from relationships.variant.links.related.

Creating webhooks

Setting up webhooks is quick and easy. You can create and manage webhooks from your Lemon Squeezy dashboard or with the API.

From the dashboard

Go to Settings > Webhooks in Lemon Squeezy and click the plus icon.

Specify the endpoint URL you want the data to be sent to in your app, then add a signing secret, which will help validate any requests to your endpoint (read on for information about signing and validating webhooks).

The last step is to select the events you want to be sent to your app. It's recommended to only select the events you need for your app, to limit the amount of requests hitting your service.

With the API

Alternatively you can use the Lemon Squeezy API to create and manage your webhooks.

For example, creating a new webhook requires a simple POST request, specifying the webhook endpoint you want data sent to, the events you want to subscribe to and a signing secret to keep webhook requests safe and secure.

POST https://api.lemonsqueezy.com/v1/webhooks { "data": { "type": "webhooks", "attributes": { "url": "https://mysite.com/webhooks/", "events": [ "order_created", "subscription_created", "subscription_updated", "subscription_expired" ], "secret": "SIGNING_SECRET" }, "relationships": { "store": { "data": { "type": "stores", "id": "1" } } } } }

Find out more about the webhooks API, including viewing, modifying and deleting webhooks, in the Webhooks API docs.

Adding an endpoint to your app

You will need an endpoint in your app that serves as the receiver for all webhook requests. This should accept POST requests and be able to process and/or save each webhook as it is sent.

To tell Lemon Squeezy that a webhook request was recieved OK, return a HTTP 200 response. Any other response code will tell Lemon Squeezy that the webhook failed and the event will be sent again up to three more times. If your app has not returned a HTTP 200 response by the fourth attempt, the event will not be tried again automatically, though you can re-send recent webhooks from your webhooks settings.

We recommend that you store, at least temporarily, webhook events locally (either in your database or a cache) so that you can return the HTTP 200 response quickly and process the data outside of the webhook request. As a bonus, if your app ever fails to process a webhook, you will then have the data to make a bug fix and process it successfully without waiting for it to be resent.

Signing and validating webhook requests

To check that data sent to your specified webhook endpoint is actually from Lemon Squeezy, you should verify the signature that is sent along with the event data.

The signature is sent in the X-Signature header and is a hash made up of the signing secret you specified when creating the webhook in your Lemon Squeezy account plus the request body.

To verify the webhook is from Lemon Squeezy you should attempt to create a matching hash and check that it matches the signature in the header.

Here is an example of creating the hash in PHP:

$secret = '[SIGNING_SECRET]'; // from your webhook settings $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.'); }

For more language examples, go to the Webhooks documentation.

Simulate subscription webhooks in Test mode

We know building an API integration can be difficult, especially when you need to test subscription webhook events that happen in the future and can’t be easily tested via the checkout.

This is why we built a webhook simulation feature. When in test mode, you can manually trigger certain subscription events from your Lemon Squeezy account.

Once you have some subscriptions in your test mode account (you can create these by purchasing products through the test mode checkout) you will see a "Simulate event" option in each subscription’s side panel.

Simply select the event you want to trigger and a matching event will be sent to your endpoint.

Note: If you want to test subscription renewal events (subscription_payment_*), your subscriptions will have to had a renewal occur first (we create the simulation webhooks based on this data). To get to this point faster, create test subscription products with daily billing intervals. This way you will get an initial renewal payment the next day and you’ll then see the option to simulate subscription_payment_* events.

Previous
Customer Portal