Webhooks are a way for an application to provide real-time notifications to other systems when certain events occur. They allow you to build integrations that respond to specific events in a more efficient and scalable way than constantly polling an API for updates.

In this guide, we’ll explore how to set up and implement webhooks, including establishing a webhook endpoint, configuring your webhooks, and handling webhook notifications. We’ll also provide examples in both JavaScript and Python.

Use cases

Webhooks can be utilized for various purposes, such as:

  • Automatically crediting a user’s account when they make a purchase.
  • Displaying a banner on your website when a user’s subscription is nearing expiration.
  • Adding new subscribers to a custom mailing list when they sign up for a subscription.

Webhook Types

If you are not developing an app, you can skip this section.

For apps, there will be two types of webhooks. One is for when a business signs up and purchases your app, and the second is when a user performs an action on a product that uses your app.

To differentiate, we have added an extra value to the webhook payload. If the webhook has to do with a user performing an action, you will see this a company ID:

  "action": "",
  "company": "biz_id",
  "data": {}

However, if it is trigged by a business that owns your app, it will be null.

  "action": "",
  "company": null,
  "data": {}

Creating a webhook

Depending on your use case, you can create a webhook for a company or an app.

Webhook Secrets

For enhanced security, you can validate that the webhook is being sent by Whop. This is a string that gets sent in the headers and is used to allow your server to verify that the request is coming from Whop. To get the signature, you need to get the webhook secret from the webhook settings.

Company Webhooks

If you are using a company webhook, you can get the secret from the developer settings page.

App webhooks

Verify the signature

The webhook secret signature will be sent in the X-Whop-Signature header. This then needs to be verified using the following code, passing in the value of the header, and the request body.

async function verifyWebhook(header, body) {
  if (!header) return null;
  const [timestampStr, signatureStr] = header.split(",");
  const [, timestamp] = timestampStr.split("=");
  const [version, sentSignature] = signatureStr.split("=");

  const now = Math.round(Date.now() / 1000);

  if (
    Number.isNaN(parseInt(timestamp)) ||
    Math.abs(now - parseInt(timestamp)) > 300
    return null; // 5 minutes buffer interval

  const stringToHash = timestamp + "." + body;

  const cryptoKey = await crypto.subtle.importKey(
    { name: "HMAC", hash: "SHA-256" },

  const signatureBuffer = await crypto.subtle.sign(
    { name: "HMAC", hash: "SHA-256" },
  const signature = buf2hex(signatureBuffer);

  if (signature !== sentSignature && version !== "v1") return null;

version is meant to equal v1. It is not a typo.