---
slug: /
title: Introduction
sidebar_position: 1
---

# Introduction

Elaras Chat is an AI chatbot platform. You configure chatbots with a personality, knowledge base, rules, and skills — then embed them on any website or integrate them into your app.

## Key concepts

**Chatbot** — an AI assistant you configure in the Elaras dashboard. Each chatbot has a public key (`cbt_...`) used for embedding and API calls.

**Session** — a conversation with a user. Identified by a UUID you generate client-side and persist across page loads. A session contains the full message history and any lead data collected.

**Skills** — actions the AI can take in your systems. You register a webhook endpoint; when the AI decides to call a skill, Elaras POSTs to your endpoint with the extracted parameters and injects the result back into the conversation.

**Webhooks** — outbound HTTP notifications sent to your server when events happen (message created, session started, lead captured). Use them to sync data to your CRM, trigger automations, or notify your team.

**Developer API** — a server-side REST API authenticated with team secret keys (`sk_...`). Use it to send messages programmatically, inspect sessions, and build backend integrations.

## Integration options

| Option | Best for |
|---|---|
| Widget (script tag) | Quick embed on any website — no build step required |
| `@elaras/chat-sdk` | Custom UI, full control, any JS framework |
| `@elaras/react-chat` | React apps — drop-in `useChat()` hook |
| Developer API | Server-side sending, backend integrations, CRM sync |

## How it works

1. You create a chatbot in the dashboard and configure its name, personality, knowledge base, and skills.
2. You embed the widget or install the SDK and initialise it with your `cbt_...` key.
3. When a user sends a message, Elaras processes it with the AI (applying your rules and knowledge base) and streams the response back via WebSocket.
4. If the AI decides to call a skill, Elaras invokes your webhook, waits for the result, and continues generating the response.
5. You receive webhook notifications for key events and can query or send messages via the Developer API.

## Authentication overview

| Context | Header | Key format |
|---|---|---|
| Widget / SDK (client-side) | `X-Chatbot-Key` | `cbt_...` |
| Developer API (server-side) | `Authorization: Bearer` | `sk_...` |
| Skills (inbound to your server) | `Authorization: Bearer` | Your own API key |
| Webhooks (inbound to your server) | `X-Elaras-Signature` | HMAC-SHA256 |

## Quick start

The fastest integration is the widget — one script tag, zero configuration required in your code:

```html
<script
  src="https://cdn.elaras.ai/widget.js"
  data-chatbot-key="cbt_your_key_here"
></script>
```

For a custom UI, see the [SDK docs](/docs/sdk) or [React hooks docs](/docs/react-hooks).


---

---
title: Widget Embed
sidebar_position: 2
---

# Widget Embed

The quickest way to add Elaras Chat to any website — one script tag, no build step, no configuration in your code.

## Installation

Add this snippet to your HTML before `</body>`:

```html
<script
  src="https://cdn.elaras.ai/widget.js"
  data-chatbot-key="cbt_your_key_here"
></script>
```

Find your chatbot key in the **Integrations** tab of your chatbot in the Elaras dashboard.

## How it works

The widget:

1. Renders inside a **Shadow DOM** so it never conflicts with your site's CSS or JavaScript.
2. Loads the chatbot config from the API (name, colours, greeting, position).
3. Opens a **WebSocket connection** for real-time streaming responses.
4. Persists the session in `localStorage` so the user's conversation history survives page reloads and navigation.

## Appearance and behaviour

All appearance and behaviour settings are configured in the Elaras dashboard — no code changes needed:

- **Name and avatar** — shown in the widget header
- **Primary colour** — button and bubble accent colour
- **Position** — bottom-right or bottom-left
- **Greeting message** — shown when the widget is first opened
- **Placeholder text** — in the message input
- **Powered by Elaras badge** — toggle on/off (Agency plan)
- **Triage questions** — optional pre-chat questions (name, email, or custom) shown before the first message

## Domain security

In your chatbot settings under **Security**, add every domain that is allowed to use the chatbot key. Requests from unlisted origins are rejected with a 401.

```
https://yoursite.com
https://app.yoursite.com
https://staging.yoursite.com
```

Use the wildcard `*` to allow all origins during local development only — never in production.

## Session persistence

Sessions are stored in `localStorage` keyed by your chatbot key:

```
elaras_session_cbt_abc123 → "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
```

The same key is used across page reloads, so the user always sees their full conversation history until they clear their browser storage or you call `window.ElarasChat.clearSession()`.

## JavaScript API

The widget exposes a small JavaScript API on `window.ElarasChat`:

```js
// Open the widget
window.ElarasChat.open();

// Close the widget
window.ElarasChat.close();

// Toggle open/closed
window.ElarasChat.toggle();

// Clear the current session (starts a fresh conversation)
window.ElarasChat.clearSession();

// Send a message programmatically
window.ElarasChat.send('Hello, I need help with my order.');

// Listen for events
window.ElarasChat.on('message', (msg) => {
  console.log('New message:', msg);
});

window.ElarasChat.on('lead', (lead) => {
  console.log('Lead captured:', lead.email);
});
```

## Events

| Event | Payload | Description |
|---|---|---|
| `open` | — | Widget opened |
| `close` | — | Widget closed |
| `message` | `{ role, content }` | New message (user or AI) |
| `lead` | `{ email, name }` | Lead email captured |
| `ready` | — | Widget fully loaded and connected |

## Content Security Policy

If you use a CSP, add these directives:

```
script-src 'self' https://cdn.elaras.ai;
connect-src 'self' https://api.elaras.ai wss://ws.elaras.ai;
```


---

---
title: JavaScript SDK
sidebar_position: 3
---

# JavaScript SDK

`@elaras/chat-sdk` is a zero-dependency TypeScript SDK that wraps the Elaras Chat API. Use it when you want to build a completely custom chat UI without the widget.

## Installation

```bash
npm install @elaras/chat-sdk
```

## Quick start — streaming

```ts
import {ChatClient} from '@elaras/chat-sdk';

const client = new ChatClient({chatbotKey: 'cbt_your_key_here'});

const sessionId = client.getOrCreateSessionId();

for await (const event of client.stream(sessionId, 'What are your opening hours?')) {
  if (event.type === 'delta') {
    process.stdout.write(event.content); // stream tokens to the UI
  } else if (event.type === 'done') {
    console.log('\nCredits used:', event.credits_used);
  } else if (event.type === 'error') {
    console.error('Error:', event.message);
  }
}
```

## Creating a client

```ts
import {ChatClient} from '@elaras/chat-sdk';

const client = new ChatClient({
  chatbotKey: 'cbt_your_key_here',
  // apiUrl defaults to 'https://api.elaras.ai'
  apiUrl: 'https://api.elaras.ai',
});
```

### Options

| Option | Type | Required | Description |
|---|---|---|---|
| `chatbotKey` | `string` | Yes | Your public chatbot key (`cbt_...`) |
| `apiUrl` | `string` | No | Override the API base URL. Defaults to `https://api.elaras.ai` |

## Methods

### `getConfig()`

Fetches the chatbot's public configuration — name, greeting, appearance settings, and lead capture setup. Called automatically by `stream()` if needed; you only need to call it directly if you want to read config values before starting a conversation.

```ts
const config = await client.getConfig();

console.log(config.name);              // "Aria"
console.log(config.greeting_message); // "Hi! How can I help?"
console.log(config.collect_leads);    // true
console.log(config.appearance?.primary_color); // "#00C4A7"
```

**Returns:** `ChatbotConfig`

```ts
interface ChatbotConfig {
  id:               string;
  name:             string;
  avatar_url?:      string | null;
  greeting_message: string | null;
  collect_leads:    boolean;
  lead_prompt:      string | null;
  appearance: {
    primary_color?: string;
    position?:      'bottom-right' | 'bottom-left';
    bubble_text?:   string;
    powered_by?:    boolean;
    theme?:         'light' | 'dark' | 'auto';
  } | null;
  triage_questions: {
    ask_name:  { enabled: boolean; question: string };
    ask_email: { enabled: boolean; question: string };
    custom:    { question: string }[];
  } | null;
}
```

---

### `stream(sessionId, message)`

Sends a message and returns an async generator that yields streaming events as the AI responds. Uses a WebSocket connection for real-time token delivery.

```ts
for await (const event of client.stream(sessionId, 'Tell me about your plans.')) {
  switch (event.type) {
    case 'delta':
      // A new token arrived — append to the current message
      appendToUI(event.content);
      break;

    case 'done':
      // Response complete
      console.log('Message ID:', event.message_id);
      console.log('Credits used:', event.credits_used);
      break;

    case 'error':
      console.error('Error code:', event.code);
      console.error('Error message:', event.message);
      break;
  }
}
```

**Event types:**

| Event | Fields | Description |
|---|---|---|
| `delta` | `content: string` | Next token chunk from the AI |
| `done` | `message_id: number`, `credits_used: number` | Response complete |
| `error` | `message: string`, `code?: string` | An error occurred |

---

### `send(sessionId, message)`

Sends a message and waits for the complete AI response synchronously (no streaming). Useful for server-side or simple integrations where you do not need real-time delivery.

```ts
const result = await client.send(sessionId, 'What are your prices?');

console.log(result.response);      // Full AI response text
console.log(result.message_id);    // Database ID of the saved message
console.log(result.credits_used);  // Credits consumed
```

**Returns:**

```ts
interface CompletionResult {
  message_id: number;
  response: string;
  credits_used: number;
}
```

---

### `getOrCreateSessionId()`

Returns the current session UUID from `localStorage`, or generates a new one if none exists. Call this once when your chat component mounts.

```ts
const sessionId = client.getOrCreateSessionId();
// e.g. "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
```

The session ID is stored in `localStorage` under `elaras_session_{chatbotKey}` and persists across page reloads.

---

### `clearSessionId()`

Removes the session UUID from `localStorage`, causing the next call to `getOrCreateSessionId()` to generate a fresh session. Use this for a "start over" button.

```ts
client.clearSessionId();
const freshSessionId = client.getOrCreateSessionId(); // New UUID
```

---

### `getSession(sessionId)`

Fetches the session and its full message history from the API.

```ts
const session = await client.getSession(sessionId);

for (const msg of session.messages) {
  console.log(`[${msg.role}] ${msg.content}`);
}
```

**Returns:**

```ts
interface Session {
  session: string;     // Session UUID
  messages: ChatMessage[];
}

interface ChatMessage {
  id: number;
  session_id: string;
  role: 'user' | 'assistant' | 'system';
  content: string;
  credits_used: number | null;
  feedback: 'up' | 'down' | null;
  created_at: string;
}
```

---

### `clearSession(sessionId)`

Deletes all messages in the session on the server. The session UUID remains valid — the next message will start a fresh conversation.

```ts
await client.clearSession(sessionId);
```

---

### `submitLead(sessionId, lead)`

Associates an email address (and optional name) with the session. Triggers the `lead.captured` webhook event.

```ts
await client.submitLead(sessionId, {
  email: 'user@example.com',
  name: 'Jordan',         // optional
});
```

---

### `submitTriage(sessionId, answers)`

Submits pre-chat triage answers collected before the conversation begins.

```ts
await client.submitTriage(sessionId, [
  {type: 'name',   question: 'What is your name?',         answer: 'Jordan'},
  {type: 'email',  question: 'What is your email?',        answer: 'jordan@example.com'},
  {type: 'custom', question: 'What can we help you with?', answer: 'Billing question'},
]);
```

Each answer has these fields:

| Field | Type | Values |
|---|---|---|
| `type` | `string` | `'name'` \| `'email'` \| `'custom'` |
| `question` | `string` | The question text |
| `answer` | `string` | The user's answer |

---

### `submitFeedback(messageId, rating)`

Records thumbs-up or thumbs-down feedback for an AI response.

```ts
await client.submitFeedback(42, 'up');   // thumbs up
await client.submitFeedback(42, 'down'); // thumbs down
```

## Full example — streaming chat UI (vanilla JS)

```ts
import {ChatClient} from '@elaras/chat-sdk';

const client = new ChatClient({chatbotKey: 'cbt_your_key_here'});
const sessionId = client.getOrCreateSessionId();

const messagesEl = document.getElementById('messages')!;
const inputEl    = document.getElementById('input')! as HTMLInputElement;
const sendBtn    = document.getElementById('send')!;

// Load history on mount
const session = await client.getSession(sessionId);
for (const msg of session.messages) {
  appendMessage(msg.role, msg.content);
}

sendBtn.addEventListener('click', async () => {
  const text = inputEl.value.trim();
  if (!text) return;

  inputEl.value = '';
  appendMessage('user', text);

  const aiEl = appendMessage('assistant', '');

  for await (const event of client.stream(sessionId, text)) {
    if (event.type === 'delta') {
      aiEl.textContent += event.content;
    } else if (event.type === 'error') {
      aiEl.textContent = 'Something went wrong. Please try again.';
    }
  }
});

function appendMessage(role: 'user' | 'assistant', content: string): HTMLElement {
  const el = document.createElement('div');
  el.className = `message message--${role}`;
  el.textContent = content;
  messagesEl.appendChild(el);
  messagesEl.scrollTop = messagesEl.scrollHeight;
  return el;
}
```

## Error handling

All methods throw a `ChatError` on non-2xx responses:

```ts
import {ChatClient, ChatError} from '@elaras/chat-sdk';

try {
  const result = await client.send(sessionId, 'Hello');
} catch (err) {
  if (err instanceof ChatError) {
    console.error(err.message);  // Human-readable message
    console.error(err.code);     // Error code (if provided)
    console.error(err.status);   // HTTP status code
  }
}
```


---

---
title: React Hooks
sidebar_position: 4
---

# React Hooks

`@elaras/react-chat` provides a `useChat()` hook and `ElarasProvider` for building chat UIs in React. It handles session management, streaming, and state internally so you can focus on your UI.

## Installation

```bash
npm install @elaras/react-chat
```

## Setup — ElarasProvider

Wrap your app (or just the part that needs the chatbot) with `ElarasProvider`:

```tsx
import {ElarasProvider} from '@elaras/react-chat';

function App() {
  return (
    <ElarasProvider chatbotKey="cbt_your_key_here">
      <YourApp />
    </ElarasProvider>
  );
}
```

### ElarasProvider props

| Prop | Type | Required | Description |
|---|---|---|---|
| `chatbotKey` | `string` | Yes | Your public chatbot key (`cbt_...`) |
| `apiUrl` | `string` | No | Override the API base URL (defaults to `https://api.elaras.ai/api`) |
| `children` | `ReactNode` | Yes | Your component tree |

## useChat()

Call `useChat()` inside any component that is a descendant of `ElarasProvider`:

```tsx
import {useChat} from '@elaras/react-chat';

function ChatWidget() {
  const {messages, send, status, isTyping, error, clearSession} = useChat();

  // ...
}
```

### Return values

| Value | Type | Description |
|---|---|---|
| `messages` | `Message[]` | All messages in the current session (user and assistant) |
| `send` | `(text: string) => void` | Send a message. Automatically appends the user message and streams the AI response |
| `status` | `ChatStatus` | Current state of the chat |
| `isTyping` | `boolean` | `true` while waiting for the first token (shows the typing indicator) |
| `error` | `string \| null` | Error message if `status === 'error'`, otherwise `null` |
| `clearSession` | `() => void` | Clear the session and start a fresh conversation |

### Message type

```ts
interface Message {
  id: string;              // Client-side UUID (for React key)
  role: 'user' | 'assistant';
  content: string;         // Full content (grows as tokens arrive during streaming)
  messageId?: number;      // Server-assigned ID (set when 'done' event arrives)
  createdAt: Date;
}
```

### ChatStatus type

```ts
type ChatStatus = 'idle' | 'typing' | 'streaming' | 'error';
```

| Status | Description |
|---|---|
| `idle` | No active request — ready to send |
| `typing` | Message sent, waiting for the first token from the AI |
| `streaming` | Receiving tokens — the last assistant message is growing |
| `error` | The last request failed — `error` contains the message |

**Tip:** Use `isTyping` (which is `status === 'typing'`) to show a typing indicator bubble before the first token arrives. Once `status === 'streaming'` the tokens are rendering in the last message directly.

## Full example

```tsx
import React, {useState, useRef, useEffect} from 'react';
import {ElarasProvider, useChat} from '@elaras/react-chat';

// Provider — put this at the top of your tree
export function ChatApp() {
  return (
    <ElarasProvider chatbotKey="cbt_your_key_here">
      <ChatUI />
    </ElarasProvider>
  );
}

// Chat UI component
function ChatUI() {
  const {messages, send, status, isTyping, error, clearSession} = useChat();
  const [input, setInput] = useState('');
  const bottomRef = useRef<HTMLDivElement>(null);

  // Scroll to bottom when messages change
  useEffect(() => {
    bottomRef.current?.scrollIntoView({behavior: 'smooth'});
  }, [messages]);

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!input.trim() || status === 'typing' || status === 'streaming') return;
    send(input.trim());
    setInput('');
  }

  return (
    <div className="chat-container">
      {/* Header */}
      <div className="chat-header">
        <h2>Chat</h2>
        <button onClick={clearSession} disabled={status !== 'idle'}>
          New conversation
        </button>
      </div>

      {/* Messages */}
      <div className="chat-messages">
        {messages.map((msg) => (
          <div key={msg.id} className={`message message--${msg.role}`}>
            {msg.content}
          </div>
        ))}

        {/* Typing indicator — shown while waiting for first token */}
        {isTyping && (
          <div className="message message--assistant message--typing">
            <span className="dot" /><span className="dot" /><span className="dot" />
          </div>
        )}

        {/* Error state */}
        {error && (
          <div className="message message--error">
            {error}
          </div>
        )}

        <div ref={bottomRef} />
      </div>

      {/* Input */}
      <form onSubmit={handleSubmit} className="chat-input-row">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message..."
          disabled={status === 'typing' || status === 'streaming'}
        />
        <button
          type="submit"
          disabled={!input.trim() || status === 'typing' || status === 'streaming'}
        >
          Send
        </button>
      </form>
    </div>
  );
}
```

## Accessing the SDK directly

If you need to call SDK methods not exposed by `useChat()` (such as `submitLead` or `submitFeedback`), use the `useElaras()` hook to get the underlying `ChatClient` instance and the current session ID:

```tsx
import {useElaras} from '@elaras/react-chat';

function LeadForm() {
  const {client, sessionId} = useElaras();

  async function handleSubmit(email: string, name: string) {
    await client.submitLead(sessionId, {email, name});
  }

  // ...
}
```

`useElaras()` returns `{ client, sessionId, resetSession }` — the same values the provider manages internally.

## TypeScript

Both `@elaras/react-chat` and `@elaras/chat-sdk` are written in TypeScript and ship full type definitions. No `@types/` package needed.

```ts
import type {Message, ChatStatus, UseChatReturn} from '@elaras/react-chat';
import type {ChatbotConfig, ChatMessage, StreamEvent} from '@elaras/chat-sdk';
```


---

---
title: Skills
sidebar_position: 5
---

# Skills

Skills let the AI take actions in your systems during a conversation. When a user asks something that requires real data — order status, account balance, appointment availability — the AI calls the relevant skill, gets the data, and continues responding with accurate information.

## How skills work

Skills are built on AI function calling:

1. You define a skill in the Elaras dashboard with a name, description, parameter schema, and your endpoint URL.
2. When the AI decides to use a skill, Elaras sends a `POST` request to your endpoint with the extracted parameters.
3. Your endpoint processes the request and returns a JSON response.
4. Elaras injects the response into the conversation context and the AI continues generating its reply.

The AI decides **when** to call a skill — you do not call it manually. The description you write tells the AI what the skill does and when to use it.

## Registering a skill

In the Elaras dashboard, go to your chatbot's **Skills** tab and click **Add skill**.

| Field | Description |
|---|---|
| **Name** | Lowercase snake_case identifier, e.g. `get_order_status`. This is what the AI sees. |
| **Description** | Plain English explanation of what the skill does and when to use it. The AI reads this to decide when to call it. |
| **Parameters** | JSON Schema object describing the parameters. The AI extracts these from the conversation. |
| **Endpoint URL** | The URL Elaras will POST to when the skill is called. Must be publicly reachable. |
| **API key** | A secret you generate. Elaras sends this as `Authorization: Bearer <api_key>` and uses it to sign the request body. |

## The request Elaras sends

When the AI calls a skill, Elaras POSTs to your endpoint:

```http
POST https://your-endpoint.com/skills/get-order-status
Authorization: Bearer your_api_key_here
X-Elaras-Signature: sha256=a1b2c3d4e5f6...
Content-Type: application/json

{
  "skill": "get_order_status",
  "params": {
    "order_id": "12345"
  },
  "session": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "lead_name": "Jordan",
    "lead_email": "jordan@example.com"
  }
}
```

| Field | Description |
|---|---|
| `skill` | The skill name as registered in the dashboard |
| `params` | The parameters the AI extracted from the conversation, matching your JSON Schema |
| `session.id` | The current session UUID |
| `session.lead_name` | Lead name if captured, otherwise `null` |
| `session.lead_email` | Lead email if captured, otherwise `null` |

## Verifying the signature

The `X-Elaras-Signature` header contains `sha256=` followed by the HMAC-SHA256 of the raw request body, using your API key as the secret. Always verify this before processing a request.

### Node.js

```js
import crypto from 'crypto';

function verifySignature(rawBody, signature, apiKey) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', apiKey)
    .update(rawBody)
    .digest('hex');

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

// Express example
app.post('/skills/get-order-status', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-elaras-signature'];

  if (!verifySignature(req.body, signature, process.env.ELARAS_SKILL_API_KEY)) {
    return res.status(401).json({error: 'Invalid signature'});
  }

  const {skill, params, session} = JSON.parse(req.body);

  // Handle the skill request...
  const order = getOrder(params.order_id);

  res.json({
    order_id: order.id,
    status: order.status,
    estimated_delivery: order.estimated_delivery,
  });
});
```

### PHP

```php
<?php

function verifyElarasSignature(string $rawBody, string $signature, string $apiKey): bool
{
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $apiKey);
    return hash_equals($expected, $signature);
}

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

if (!verifyElarasSignature($rawBody, $signature, $apiKey)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$payload = json_decode($rawBody, true);
$params  = $payload['params'];

// Handle the skill request...
$order = getOrder($params['order_id']);

header('Content-Type: application/json');
echo json_encode([
    'order_id'           => $order['id'],
    'status'             => $order['status'],
    'estimated_delivery' => $order['estimated_delivery'],
]);
```

## Expected response

Return any JSON object or string. The response is passed directly to the AI as the skill result:

```json
{
  "order_id": "12345",
  "status": "shipped",
  "estimated_delivery": "2026-07-26",
  "tracking_url": "https://track.example.com/12345"
}
```

The AI will then incorporate this data into its next response to the user.

## Parameter JSON Schema example

Here is a complete skill definition for an order status lookup:

**Name:** `get_order_status`

**Description:** Look up the status of a customer order. Call this when the user asks about their order, delivery, or tracking information.

**Parameters schema:**

```json
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "description": "The order ID or order number mentioned by the user."
    }
  },
  "required": ["order_id"]
}
```

The AI extracts `order_id` from the conversation (e.g. "my order 12345") and passes it in `params.order_id`.

## Error handling

If your skill encounters an error, return a descriptive error message as a string or JSON — do **not** return a 4xx or 5xx HTTP status. A non-2xx response tells Elaras the skill invocation itself failed (network/infra error), which is different from a business logic error.

Good:

```json
{ "error": "Order 99999 not found. Please check the order number and try again." }
```

Bad (causes a skill invocation failure, retried by Elaras):

```
HTTP 404 Not Found
```

## Testing skills

Use the **Test** button on the skill configuration page in the dashboard. You can send a test payload with custom `params` and inspect the response your endpoint returns. The test call is signed identically to real calls so your signature verification will run.

## Best practices

- **Write a precise description.** The AI uses it to decide when to call the skill. Be specific about what data the skill provides and what questions it answers.
- **Keep responses concise.** Return only the data the AI needs to answer the question. Large payloads consume more context window and credits.
- **Validate params server-side.** The AI extracts params from natural language — always validate and sanitise them before using them in database queries.
- **Return actionable error messages.** If something goes wrong, return a message the AI can relay to the user ("I could not find that order — could you double-check the number?").
- **Respond within 10 seconds.** Elaras times out skill calls after 10 seconds. For long-running operations, fetch the data asynchronously before the conversation starts or cache recent results.


---

---
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.


---

---
title: Developer API
sidebar_position: 7
---

# Developer API

The Developer API is a server-side REST API for managing chat sessions and sending messages programmatically. It is authenticated with a team secret key (`sk_...`) and should only ever be called from your server — never from client-side code.

## Creating an API key

1. Go to **Developer > API Keys** in the Elaras dashboard.
2. Click **Create API key** and give it a descriptive name (e.g. "Production backend").
3. Copy the key immediately — it is only shown once.

API keys are scoped to your team and have access to all chatbots in the team.

## Authentication

Pass your secret key as a Bearer token in every request:

```http
Authorization: Bearer sk_live_your_key_here
```

```bash
# Example with curl
curl https://api.elaras.ai/api/developer/v1/sessions \
  -H "Authorization: Bearer sk_live_your_key_here"
```

## Base URL

```
https://api.elaras.ai/api
```

## Endpoints

### List sessions

Returns a paginated list of all sessions across all chatbots for your team.

```http
GET /developer/v1/sessions
```

**Query parameters:**

| Parameter | Type | Default | Description |
|---|---|---|---|
| `page` | integer | `1` | Page number |
| `per_page` | integer | `15` | Results per page (max 100) |
| `chatbot_id` | string | — | Filter by chatbot key (`cbt_...`) |

**Example request:**

```bash
curl "https://api.elaras.ai/api/developer/v1/sessions?per_page=25&chatbot_id=cbt_abc123" \
  -H "Authorization: Bearer sk_live_your_key_here"
```

**Example response:**

```json
{
  "data": [
    {
      "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "messages": [
        {
          "id": 1,
          "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "role": "user",
          "content": "What are your opening hours?",
          "credits_used": null,
          "feedback": null,
          "created_at": "2026-07-24T10:29:58Z"
        },
        {
          "id": 2,
          "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "role": "assistant",
          "content": "We are open Monday to Friday, 9am to 5pm.",
          "credits_used": 3,
          "feedback": "up",
          "created_at": "2026-07-24T10:30:01Z"
        }
      ]
    }
  ],
  "total": 120,
  "per_page": 25,
  "current_page": 1
}
```

---

### Get a session

Returns a single session with its full message history.

```http
GET /developer/v1/sessions/{session_id}
```

**Path parameters:**

| Parameter | Description |
|---|---|
| `session_id` | Session UUID |

**Example request:**

```bash
curl "https://api.elaras.ai/api/developer/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer sk_live_your_key_here"
```

**Example response:**

```json
{
  "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "messages": [
    {
      "id": 1,
      "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "role": "user",
      "content": "What are your opening hours?",
      "credits_used": null,
      "feedback": null,
      "created_at": "2026-07-24T10:29:58Z"
    }
  ]
}
```

---

### Send a message to a session

Sends a message to a session server-side. The AI processes it and delivers the response via WebSocket to the session's channel — the response is **not** included in the HTTP response body.

This is useful for proactive messages (notifying a user when their order ships), backend-triggered conversations, or injecting context into a session before a user starts chatting.

```http
POST /developer/v1/sessions/{session_id}/messages
```

**Path parameters:**

| Parameter | Description |
|---|---|
| `session_id` | Session UUID. Generate a UUID server-side and store it alongside the user record in your system. |

**Request body:**

```json
{
  "message": "Your order #12345 has shipped! Expected delivery: 26 July.",
  "chatbot_key": "cbt_abc123"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | Yes | The message content to send |
| `chatbot_key` | string | Conditional | Required if the session does not exist yet. The `cbt_...` key of the chatbot to use. |

**Example request:**

```bash
curl -X POST "https://api.elaras.ai/api/developer/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/messages" \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Your order has shipped!",
    "chatbot_key": "cbt_abc123"
  }'
```

**Example response:**

```json
{
  "ok": true
}
```

The AI response is delivered asynchronously via WebSocket to the channel `chat.{session_id}`. If the user has the widget or SDK open, they will receive the response in real time.

## Node.js example

```ts
// Send a proactive message when an order ships
async function notifyOrderShipped(sessionId: string, orderId: string) {
  const response = await fetch(
    `https://api.elaras.ai/api/developer/v1/sessions/${sessionId}/messages`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.ELARAS_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        message: `Your order #${orderId} has shipped! I can help you track it or answer any questions.`,
        chatbot_key: process.env.ELARAS_CHATBOT_KEY,
      }),
    },
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Elaras API error: ${error.message}`);
  }

  return response.json();
}
```

## PHP example

```php
<?php

function sendElarasMessage(string $sessionId, string $message, string $chatbotKey): void
{
    $url = "https://api.elaras.ai/api/developer/v1/sessions/{$sessionId}/messages";

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . getenv('ELARAS_SECRET_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS     => json_encode([
            'message'     => $message,
            'chatbot_key' => $chatbotKey,
        ]),
    ]);

    $response = curl_exec($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($status !== 200) {
        $body = json_decode($response, true);
        throw new \RuntimeException('Elaras API error: ' . $body['message']);
    }
}

// Usage — e.g. in an order shipped event handler
sendElarasMessage(
    $user->elaras_session_id,
    "Your order #{$order->id} has shipped!",
    config('services.elaras.chatbot_key'),
);
```

## Skills API

You can manage skills programmatically instead of through the dashboard — useful for multi-tenant setups where each customer needs their own skill configuration.

### List chatbots

```http
GET /developer/v1/chatbots
```

Returns all chatbots for your team. Use the `id` field in the skill routes below.

```bash
curl "https://api.elaras.ai/api/developer/v1/chatbots" \
  -H "Authorization: Bearer sk_live_your_key_here"
```

---

### List skills

```http
GET /developer/v1/chatbots/{chatbot_id}/skills
```

---

### Create a skill

```http
POST /developer/v1/chatbots/{chatbot_id}/skills
```

**Request body:**

```json
{
  "name": "get_order_status",
  "description": "Look up the status of a customer order by order ID. Call this when the user asks about their order, delivery, or shipment.",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "The order ID to look up"
      }
    },
    "required": ["order_id"]
  },
  "endpoint_url": "https://your-api.com/skills/order-status",
  "api_key": "your_secret_key",
  "enabled": true
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Lowercase `snake_case`, max 64 chars. This is the function name the AI uses. |
| `description` | string | Yes | Plain English explanation. The AI reads this to decide when to call the skill. |
| `parameters` | object | No | [JSON Schema](https://json-schema.org) object describing the parameters the AI should extract. |
| `endpoint_url` | string | Yes | The URL Elaras will POST to when the AI calls this skill. Must be publicly reachable. |
| `api_key` | string | No | Sent as `Authorization: Bearer <api_key>` to your endpoint. Stored encrypted. |
| `enabled` | boolean | No | Defaults to `true`. |

---

### Update a skill

```http
PUT /developer/v1/chatbots/{chatbot_id}/skills/{skill_id}
```

Same fields as create — all are optional (partial update).

---

### Delete a skill

```http
DELETE /developer/v1/chatbots/{chatbot_id}/skills/{skill_id}
```

Returns `204 No Content`.

---

## Use cases

**Proactive notifications** — When an event happens in your system (order shipped, appointment reminder, support ticket updated), send a message to the user's session so they see it the next time they open the chat.

**Backend-triggered conversations** — Create sessions and prime them with context before the user starts chatting. For example, inject the user's account details and recent orders so the AI can answer questions without asking for information the user has already provided.

**CRM integrations** — Read session data via `GET /developer/sessions` and sync conversations into your CRM, helpdesk, or data warehouse.

**Admin tooling** — Build internal tools that let your team view chat sessions, inspect what the AI said, or send messages on behalf of the chatbot.

## Error responses

All errors follow the same shape:

```json
{
  "message": "Human-readable error description.",
  "code": "OPTIONAL_ERROR_CODE"
}
```

| HTTP status | Meaning |
|---|---|
| `401` | Missing or invalid `sk_` key |
| `404` | Session or resource not found |
| `422` | Validation error (check `message` for details) |
| `429` | Rate limit exceeded — back off and retry |
| `500` | Elaras server error — retry with exponential backoff |
