---
title: Webhooks
sidebar_position: 6
---

# Webhooks

Webhooks let Elaras notify your server when events happen in real time. Use them to sync conversations to your CRM, trigger automations, notify your team in Slack, or update your database when a lead is captured.

## How webhooks work

1. You register a webhook URL in the Elaras dashboard under **Developer > Webhooks**.
2. When an event occurs, Elaras sends a `POST` request to your URL with a JSON payload and a signature header.
3. Your server verifies the signature and processes the event.
4. You respond with any 2xx status within 15 seconds.

If your server does not respond with 2xx, Elaras retries the delivery.

## Configuring webhooks

Go to **Developer > Webhooks** in the Elaras dashboard and click **Add webhook**:

| Field | Description |
|---|---|
| **URL** | Your publicly reachable HTTPS endpoint |
| **Secret** | A secret you generate and store. Used to sign payloads — treat it like a password. |
| **Events** | Choose which events trigger this webhook (or select "All events") |

## Events

### `message.created`

Fired when a new message is saved (user or AI response).

```json
{
  "event": "message.created",
  "chatbot_id": "cbt_abc123",
  "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "data": {
    "message_id": 42,
    "role": "assistant",
    "content": "We are open Monday to Friday, 9am to 5pm.",
    "credits_used": 3,
    "created_at": "2026-07-24T10:30:00Z"
  }
}
```

### `session.started`

Fired when a new session sends its first message.

```json
{
  "event": "session.started",
  "chatbot_id": "cbt_abc123",
  "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "data": {
    "started_at": "2026-07-24T10:29:55Z"
  }
}
```

### `lead.captured`

Fired when a user submits their email via the lead capture form or the SDK's `submitLead()` method.

```json
{
  "event": "lead.captured",
  "chatbot_id": "cbt_abc123",
  "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "data": {
    "email": "user@example.com",
    "name": "Jordan",
    "captured_at": "2026-07-24T10:31:00Z"
  }
}
```

## Payload structure

Every webhook payload shares this top-level shape:

| Field | Type | Description |
|---|---|---|
| `event` | `string` | Event type (`message.created`, `session.started`, `lead.captured`) |
| `chatbot_id` | `string` | The chatbot's public key (`cbt_...`) |
| `session_id` | `string` | Session UUID |
| `data` | `object` | Event-specific data |

## Signature verification

Elaras signs every webhook request with HMAC-SHA256 using your webhook secret. The signature is in the `X-Elaras-Signature` header:

```
X-Elaras-Signature: sha256=a1b2c3d4e5f6...
```

**Always verify the signature before processing a webhook.** This prevents malicious third parties from sending fake events to your endpoint.

### Node.js

```js
import crypto from 'crypto';
import express from 'express';

const app = express();

app.post('/webhooks/elaras', express.raw({type: 'application/json'}), (req, res) => {
  const signature     = req.headers['x-elaras-signature'];
  const webhookSecret = process.env.ELARAS_WEBHOOK_SECRET;

  const expected = 'sha256=' + crypto
    .createHmac('sha256', webhookSecret)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(req.body);

  switch (payload.event) {
    case 'message.created':
      console.log('New message:', payload.data.content);
      break;

    case 'session.started':
      console.log('New session:', payload.session_id);
      break;

    case 'lead.captured':
      console.log('Lead:', payload.data.email);
      // e.g. add to your CRM
      break;
  }

  res.status(200).send('ok');
});
```

### PHP

```php
<?php

$rawBody       = file_get_contents('php://input');
$signature     = $_SERVER['HTTP_X_ELARAS_SIGNATURE'] ?? '';
$webhookSecret = getenv('ELARAS_WEBHOOK_SECRET');

$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $webhookSecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    echo 'Invalid signature';
    exit;
}

$payload = json_decode($rawBody, true);

switch ($payload['event']) {
    case 'message.created':
        error_log('New message: ' . $payload['data']['content']);
        break;

    case 'session.started':
        error_log('New session: ' . $payload['session_id']);
        break;

    case 'lead.captured':
        error_log('Lead: ' . $payload['data']['email']);
        // Add to your CRM...
        break;
}

http_response_code(200);
echo 'ok';
```

## Retry behaviour

If your endpoint does not return a 2xx response (or times out after 15 seconds), Elaras automatically retries:

| Attempt | Delay after previous attempt |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |

After 3 failed retries (4 total attempts), the delivery is marked as failed and no further attempts are made. You can view failed deliveries and manually re-trigger them from **Developer > Webhooks > Delivery log**.

## Responding

- Respond with any **2xx** status code (`200`, `204`, etc.) to acknowledge receipt.
- Respond **within 15 seconds** — process the event asynchronously if needed (queue it, then respond 200 immediately).
- The response body is ignored by Elaras.

## Security tips

- Store your webhook secret in an environment variable — never hardcode it.
- Use `hash_equals()` / `timingSafeEqual()` for comparison — never `===` or `==`. This prevents timing attacks.
- Only accept HTTPS endpoints.
- Use a dedicated endpoint for Elaras webhooks rather than a shared route.
