# Webhooks

<figure><img src="/files/Z1CjDCAQKYMJJFyCPVxN" alt=""><figcaption></figcaption></figure>

Webhooks let you receive real-time, signed HTTP POST notifications whenever key demo and showcase events occur with no polling required.

{% hint style="info" %}
**Webhooks are available on the Scale plan and above.** You must be a workspace Admin or Creator to configure webhooks.
{% endhint %}

***

#### Overview

When an identified viewer interacts with your demos or showcases, Supademo can push event data directly to any HTTPS endpoint you control. Each delivery is signed so you can verify it came from Supademo.

**Common use-cases:**

* Sync leads into your CRM in real time
* Trigger internal Slack or email alerts when a demo is viewed
* Feed engagement data into a data warehouse or BI tool
* Kick off downstream automations in your own backend

***

#### Getting started

**1. Navigate to Integrations**

Open your workspace **Settings → Integrations** page and locate the **Webhook** card.

**2. Create a webhook**

Click **Connect** to open the Webhook Setup modal, then:

1. **Webhook URL** — Enter the HTTPS endpoint that will receive events (e.g. `https://yourapp.com/webhooks/supademo`). The URL must use HTTPS and cannot target private/reserved hosts.
2. **Enable deliveries** — Toggle deliveries on or off. You can pause deliveries at any time without losing your configuration.
3. **Select events** — Choose which events should trigger a delivery (see Supported events below).
4. Click **Create**.
5. Copy the signing secret

After creation, a **signing secret** (prefixed `whsec_`) is displayed **once**. Copy it immediately and store it securely — you will need it to verify incoming webhook signatures.

{% hint style="warning" %}
**The signing secret is only shown once.** If you lose it, you will need to delete the webhook and create a new one to generate a fresh secret.
{% endhint %}

**4. Send a test event**

Click **Test** in the setup modal to send a signed `webhook.test` event to your endpoint. Use this to confirm your server is reachable and responding with a `2xx` status code.

***

#### Supported events

| Event name        | Label           | Description                                               |
| ----------------- | --------------- | --------------------------------------------------------- |
| `lead.captured`   | Lead captured   | A viewer submits a form inside a demo                     |
| `demo.viewed`     | Demo viewed     | An identified viewer opens a demo                         |
| `demo.completed`  | Demo completed  | An identified viewer session ends with engagement metrics |
| `showcase.viewed` | Showcase viewed | An identified viewer opens a showcase                     |
| `webhook.test`    | Test            | Sent when you click **Test** in the setup modal           |

{% hint style="info" %}
**Events only fire for identified viewers.** Anonymous views do not trigger webhook deliveries.
{% endhint %}

***

#### Payload format

Every webhook delivery is an HTTP `POST` with a JSON body. Below is an example `lead.captured` payload:

```json
{
  "event": "lead.captured",
  "workspaceId": "clx...",
  "viewerId": "viewer_abc123",
  "email": "jane@example.com",
  "name": "Jane Smith",
  "company": "Acme Inc",
  "viewer": {
    "id": "viewer_abc123",
    "email": "jane@example.com",
    "first_name": "Jane",
    "last_name": "Smith",
    "company": "Acme Inc",
    "job_title": "Product Manager",
    "custom_fields": {
      "use_case": "Onboarding"
    }
  },
  "entityType": "demo",
  "entityId": "demo_xyz",
  "entityTitle": "Product Tour",
  "entityLink": "https://app.supademo.com/demo/demo_xyz",
  "entityAnalyticsLink": "https://app.supademo.com/demo/demo_xyz?analytics=true",
  "entityViewedAt": "2025-06-01T14:30:00.000Z",
  "utmParams": {
    "utm_source": "linkedin",
    "utm_medium": "social",
    "utm_campaign": "launch",
    "utm_term": null,
    "utm_content": null,
    "gclid": null,
    "fbclid": null,
    "msclkid": null
  },
  "customParams": {},
  "location": {
    "country": "US",
    "city": "San Francisco"
  },
  "device": {
    "browser": "Chrome",
    "os": "macOS"
  }
}
```

**Viewer fields**

The `viewer` object can contain any of the following fields, depending on which form fields are configured in your demo:

| Field                 | Type     | Description                             |
| --------------------- | -------- | --------------------------------------- |
| `id`                  | `string` | Unique viewer identifier                |
| `email`               | `string` | Viewer's email address                  |
| `first_name`          | `string` | First name                              |
| `last_name`           | `string` | Last name                               |
| `company`             | `string` | Company name                            |
| `role`                | `string` | Role                                    |
| `job_title`           | `string` | Job title                               |
| `phone`               | `string` | Phone number                            |
| `mobile_phone`        | `string` | Mobile phone number                     |
| `website`             | `string` | Website URL                             |
| `address`             | `string` | Street address                          |
| `city`                | `string` | City                                    |
| `state`               | `string` | State or region                         |
| `country`             | `string` | Country                                 |
| `postal_code`         | `string` | Postal/ZIP code                         |
| `industry`            | `string` | Industry                                |
| `lead_source`         | `string` | Lead source                             |
| `annual_revenue`      | `string` | Annual revenue                          |
| `number_of_employees` | `string` | Number of employees                     |
| `number_of_locations` | `string` | Number of locations                     |
| `department`          | `string` | Department                              |
| `seniority_level`     | `string` | Seniority level                         |
| `custom_fields`       | `object` | Key-value map of any custom form fields |

***

#### Verifying webhook signatures

Every delivery includes an `x-supademo-signature` header. Use your signing secret to verify that the request genuinely came from Supademo.

The signature is an HMAC-SHA256 hex digest of the raw request body, computed with your `whsec_` signing secret as the key.

**Example verification (Node.js)**

```javascript
import crypto from "node:crypto";

function verifyWebhookSignature({ body, signature, secret }) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your request handler:
const body = await request.text();
const signature = request.headers.get("x-supademo-signature");

if (!verifyWebhookSignature({ body, signature, secret: process.env.SUPADEMO_WEBHOOK_SECRET })) {
  return new Response("Invalid signature", { status: 401 });
}

const payload = JSON.parse(body);
// Process the verified payload...
```

**Example verification (Python)**

```python
import hmac
import hashlib

def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
```

{% hint style="info" %}
**Always use constant-time comparison** (e.g. `crypto.timingSafeEqual` or `hmac.compare_digest`) to prevent timing attacks.
{% endhint %}

***

#### Managing your webhook

**Update settings**

Click the **gear icon** on the Webhook integration card to reopen the setup modal. You can change the endpoint URL, toggle deliveries, or update event subscriptions at any time.

**Pause deliveries**

Toggle **Enable webhook deliveries** off to temporarily stop all deliveries without deleting the webhook. Your URL, signing secret, and event selections are preserved.

**Delete a webhook**

To remove a webhook entirely, click **Disconnect** on the integration card. This permanently deletes the configuration and signing secret.

***

#### Troubleshooting

| Issue                          | Solution                                                                                                            |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| Test delivery fails            | Ensure your endpoint is publicly reachable, uses HTTPS, and returns a `2xx` status code                             |
| No events received             | Confirm deliveries are **enabled** and at least one event type is selected                                          |
| Signature mismatch             | Verify you are using the raw request body (not a parsed/re-serialized version) with the correct signing secret      |
| "Webhook already exists" error | Each workspace supports one webhook. Delete the existing one before creating a new one                              |
| URL validation error           | The URL must use HTTPS, cannot include credentials (`user:pass@`), and cannot target localhost or private IP ranges |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.supademo.com/admin-and-billing/integrations/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
