Light Docs

Webhooks

Guide to using webhooks to notify your application about store events.

Overview

Webhooks are a way to notify your applications, or webhook-enabled portals - like Discord - of events in your store.

You may configure webhooks to be triggered on specific events, such as when a product is created or updated, or when an order is completed (full list of events).

Webhooks are defined in the dashboard and take the following values:

  • Event - Event to trigger this webhook on
  • Method - HTTP method to use when sending the webhook (POST, GET, PUT, DELETE, PATCH)
  • URL - URL to send the webhook to
  • Payload Template - JSON body template for the payload to send. You may use placeholders to include dynamic data in the payload. Only supported for POST, PUT and PATCH methods.
  • Headers - Optional additional headers, in JSON form

Discord Example

You may easily set up Discord notifications using webhooks. This example covers creating a webhook notification for completed orders. However, other events can be used in a similar manner, simply with different body placeholders.

Create the webhook in Discord (Server Settings > Integrations), copy the webhook URL and create a new webhook in the admin dashboard with the following values:

  • Event: order.completed
  • Method: POST
  • URL: Your Discord webhook URL
  • Payload Template:
{
	"content": "New order completed by %%USER_NAME%% (%%USER_EMAIL%%) for %%TOTAL%%! Items: %%ITEMS%%"
}
  • Headers (leave empty)

Events

The following events can be used as webhook triggers. Each has a set of predefined placeholders you can use in the payload template.

Security

In order to use webhooks as an "only source of truth" you need to trust the data the webhook is sending (for example, if your app relies on "order completed" webhooks to give out licenses), you need to validate the webhook requests coming to your application.

This verification is crucial to block attempts at webhook forgery, where a malicious actor creates a valid-looking body, sends it to your webhook endpoint and gets a free license.

Do I need verification?

Technically, no. Verifying these headers is only essential for webhooks that trigger sensitive actions in your application, such as license generation or user creation. If your webhook is only used for logging or notifications, you may skip this step - the webhooks will work regardless - however, you're at risk of receiving forged data in your notifications.

Doing so is quite straightforward, but the implementation varies on the programming language and framework you are using. The general idea boils down to:

  • Each webhook has a "Signing Secret" assigned to it. You can find it in the webhooks tab in the admin dashboard.
  • A X-Webhook-Signature header is sent with each webhook request. It contains a HMAC SHA256 hash of the request body JSON, signed with the signing secret.
  • To validate the authority of the request, you need to compute your own HMAC SHA256 hash of the request body on your side, using the same signing secret.
  • You may then compare your own hash to the value sent in the X-Webhook-Signature header. If they match, the request is valid.

Example of validation in JavaScript:

import crypto from "crypto"; // or const crypto = require("crypto"); for CommonJS

// Your webhook signing secret from the admin dashboard
const SIGNING_SECRET = "e68007b0fb5...";

/// ...

// In your request handler:
app.post("/webhook", (req, res) => {
	// The signature sent in the request headers
	const signature = req.headers["x-webhook-signature"];
	// The raw request body as a string or Buffer
	const body = req.rawBody;
	const hash = crypto.createHmac("sha256", SIGNING_SECRET).update(body).digest("hex");
	if (hash !== signature) {
		// Invalid request, request was possibly forged
		res.status(401).send("Invalid signature");
		return;
	}
	// Valid request, proceed with processing
	/// ...
	// Respond with 200 code
	res.status(200).send("OK");
});

Caveats

The signature is signed with the raw request body, with unescaped slashes, unescaped unicode characters and not pretty-printed in any way. Make sure you're using the same format when computing the hash on your side.