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
- You register a webhook URL in the Elaras dashboard under Developer > Webhooks.
- When an event occurs, Elaras sends a
POSTrequest to your URL with a JSON payload and a signature header. - Your server verifies the signature and processes the event.
- 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 |
| Events | Choose which events trigger this webhook (or select "All events") |
Elaras generates a signing secret automatically when you create the webhook. Copy it from the dashboard — it is only shown once. Store it as an environment variable on your server.
Events
message.created
Fired when an AI response is saved to a session.
{ "event": "message.created", "timestamp": "2026-07-24T10:30:00+00:00", "data": { "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "chatbot_id": 1, "message": { "id": 42, "role": "assistant", "content": "We are open Monday to Friday, 9am to 5pm.", "credits_used": 3 } } }
session.started
Fired when a new session sends its first message.
{ "event": "session.started", "timestamp": "2026-07-24T10:29:55+00:00", "data": { "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "chatbot_id": 1 } }
lead.captured
Fired when a user submits their contact details via the lead capture form or the SDK's submitLead() method.
{ "event": "lead.captured", "timestamp": "2026-07-24T10:31:00+00:00", "data": { "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "chatbot_id": 1, "name": "Jordan", "email": "[email protected]" } }
human.takeover
Fired when an operator claims a conversation from the inbox.
{ "event": "human.takeover", "timestamp": "2026-07-24T10:32:00+00:00", "data": { "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "chatbot_id": 1, "operator_name": "Alice", "taken_over_by": "[email protected]" } }
conversation.flagged
Fired when the AI flags a conversation for review.
{ "event": "conversation.flagged", "timestamp": "2026-07-24T10:33:00+00:00", "data": { "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "chatbot_id": 1, "flag_id": 7, "category": "off_topic", "severity": "medium", "reason": "User is asking for legal advice outside the agent's scope." } }
Payload structure
Every webhook payload shares this top-level shape:
| Field | Type | Description |
|---|---|---|
event | string | Event type |
timestamp | string | ISO 8601 timestamp of when the event was dispatched |
data | object | Event-specific data |
Elaras also enriches data with session context where available:
| Field | Description |
|---|---|
session_url | Link to the conversation in the Elaras dashboard |
lead_name | Visitor's name if captured |
lead_email | Visitor's email if captured |
first_user_message | First message the visitor sent (up to 200 chars) |
last_user_message | Most recent message the visitor sent (up to 200 chars) |
Signature verification
Elaras signs every webhook request with HMAC-SHA256 using the webhook secret. The signature is in the X-Elaras-Signature header:
X-Elaras-Signature: sha256=a1b2c3d4e5f6...
A second header, X-Elaras-Event, contains the event name as a convenience for routing.
Always verify the signature before processing a webhook. This prevents malicious third parties from sending fake events to your endpoint.
Node.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.message.content); break; case 'session.started': console.log('New session:', payload.data.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 $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']['message']['content']); break; case 'session.started': error_log('New session: ' . $payload['data']['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 |
After 3 total attempts (1 initial + 2 retries), the delivery is marked as failed and no further attempts are made.
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.
