# Webhooks

You can send demo events to the Webhook URL.

{% hint style="warning" %}

### **Identifying Leads**

There are a few ways in which you can capture or identify leads. You can refer to the article on [Lead Tracking](https://docs.storylane.io/analytics-and-performance/capture-leads) to learn more
{% endhint %}

***

### **How to Integrate**

* Click the "Settings" on the left-hand menu, and then click on the "Integration" tab.&#x20;
* Choose Webhook integration and click on the "Connect" button.&#x20;

<div align="left"><figure><img src="https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FsZYWdyFZaKGgkShqMY0C%2FScreenshot%202025-11-21%20at%2015.48.39.png?alt=media&#x26;token=82935544-a8d5-4ddf-80e6-f2ac64b1adfe" alt="" width="563"><figcaption></figcaption></figure></div>

* Paste the webhook URL (This is the URL where you want to receive webhooks)
* Lastly, decide whether you want to send webhooks for all demo sessions (unknown and known), or only when the lead or account is known.

***

### How do I verify the webhook signature? <a href="#how-do-i-verify-the-webhook-signature" id="how-do-i-verify-the-webhook-signature"></a>

To ensure that webhook requests are coming from Storylane and have not been tampered with, we recommend verifying the signature included in the request headers.

Every webhook request includes an `x-storylane-signature` header. This signature is an HMAC-SHA256 hash of the raw request body, generated using your Webhook Verification Secret and then Base64 encoded.

#### 1. Retrieve your Verification Secret

You can find your Verification Secret in the Storylane dashboard under **Settings > Integrations > Webhook**. Keep this secret secure; do not share it or commit it to public repositories.

#### 2. Verification Logic

To verify the signature:

1. Retrieve the `x-storylane-signature` header from the request.
2. Get the raw body of the request (unparsed string).
3. Compute the HMAC-SHA256 hash of the raw body using your Verification Secret as the key.
4. Base64 encode the resulting hash.
5. Compare your generated string with the value in the `x-storylane-signature` header.

#### 3. Implementation Examples

**Node.js (Express)**

When using Express, ensure you use the raw body for the HMAC calculation.

```js
const crypto = require('crypto');

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-storylane-signature'];
  const secret = process.env.STORYLANE_WEBHOOK_SECRET;

  // 'req.rawBody' should be the unparsed string of the request body
  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(req.rawBody).digest('base64');

  if (signature === digest) {
    // Valid signature
    console.log('Webhook verified');
    res.status(200).send('Verified');
  } else {
    // Invalid signature
    res.status(401).send('Unauthorized');
  }
});
```

**Ruby**

```ruby
require 'openssl'
require 'base64'

def verify_signature(payload, secret, received_signature)
  hmac = OpenSSL::HMAC.digest(
    'sha256',
    secret.to_s,
    payload
  )

  expected_signature = Base64.strict_encode64(hmac)

  # Use secure comparison to prevent timing attacks
  Rack::Utils.secure_compare(expected_signature, received_signature)
end
```

**Python (Flask)**

```python
import hmac
import hashlib
import base64
from flask import request

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    secret = "your_verification_secret"
    received_signature = request.headers.get('x-storylane-signature')

    # Get raw data
    payload = request.get_data()

    # Create hash
    digest = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).digest()

    expected_signature = base64.b64encode(digest).decode()

    if hmac.compare_digest(expected_signature, received_signature):
        return "OK", 200
    else:
        return "Unauthorized", 401
```

***

### Webhook Example&#x20;

To help you understand what kind of data you can expect from our webhooks, here's a sample response payload for unknown and known demo sessions.

#### **Known demo sessions (known lead data)**

```json
{
  "id": "2f37f8ce-6ecb-49a2-be12-36ae9c063042",
  "buyer_reveal": {
    "company_name": "Acme Inc",
    "company_domain": "example.com",
    "company_annual_revenue_range": "1M-10M",
    "company_employee_range": "11-50"
  },
  "lead": {
    "id": "f3694255-aed4-4514-8ce6-ac55506a67fe",
    "client_source": "none",
    "client_tracking_id": null,
    "email": "john.doe@example.com",
    "first_name": "John",
    "last_name": "Doe"
  },
  "name": "Product Demo",
  "link": "https://app.storylane.io/share/aejr8lqfghlp",
  "viewed_at": "2024-05-17T10:58:05.337+00:00",
  "completion": 30,
  "time_spent": 125,
  "checklist_completed": false,
  "cta_opened": "https://example.com",
  "analytics_url": "https://app.storylane.io/analytics/activity/2f37f8ce-6ecb-49a2-be12-36ae9c063042",
  "utm_params": {
    "gclid": "123xyz",
    "utm_source": "source",
    "utm_medium": "medium",
    "utm_campaign": "campaign",
    "utm_term": "term",
    "utm_content": "content"
  }
}
```

#### &#x20;**Unknown demo sessions (unknown lead data)**

```json
{
  "id": "2f37f8ce-6ecb-49a2-be12-36ae9c063042",
  "buyer_reveal": null,
  "lead": null,
  "name": "Product Demo",
  "link": "https://app.storylane.io/share/aejr8lqfghlp",
  "viewed_at": "2024-05-17T10:58:05.337+00:00",
  "completion": 30,
  "time_spent": 125,
  "checklist_completed": false,
  "cta_opened": "https://example.com",
  "analytics_url": "https://app.storylane.io/analytics/activity/2f37f8ce-6ecb-49a2-be12-36ae9c063042",
  "utm_params": {
    "gclid": "123xyz",
    "utm_source": "source",
    "utm_medium": "medium",
    "utm_campaign": "campaign",
    "utm_term": "term",
    "utm_content": "content"
  }
}
```


---

# 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.storylane.io/integrations/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.
