Chat actions
Chat widgets can do more than answer from your sources. Under Reply → What it can do you can add optional actions:
| Action | What it does |
|---|---|
| Web search | Lets the chatbot look up live pages on hosts you allowlist (max 5) |
| Webhook | Sends structured details to your HTTPS endpoint (tickets, CRM, n8n, Make, …) |
Knowledge search stays always on. Secrets never appear in the public widget config API.
You can add up to 5 actions per chatbot, including multiple webhooks with different labels and fields.
#Web search
Configure one or more hostnames (for example status.example.com). The model may only search those hosts. It is meant for live content that is not (yet) in your knowledge base, not as a general open-web browse.
#Webhooks
When a visitor clearly wants the action (book a demo, open a ticket, leave details), the chatbot collects the fields you configured and POSTs JSON to your URL. It should ask for missing required values and must not invent placeholders.
#Setup
- Open the chatbot widget → Reply → What it can do → Add action → Webhook.
- Set a clear label (the model sees this) and an optional description of when to use it.
- Paste an HTTPS URL.
- Generate the signing secret (
dl_wh_…) and store it on your receiver. Datalumo encrypts it at rest and only shows it once. - Add the fields the model should collect (text, email, choice, or yes/no). At least one field is required; max 8.
#Request
POST https://your-app.example/hooks/datalumo
Content-Type: application/json
X-Datalumo-Timestamp: 1735689600
X-Datalumo-Signature: v1=…
Body:
{
"tool_id": "01h…",
"tool_label": "Create support ticket",
"conversation_id": "…",
"answer_id": "…",
"page_url": "https://example.com/pricing",
"user": { "id": "user-42" },
"context": { "title": "Pricing" },
"payload": {
"email": "[email protected]",
"message": "Billing failed on invoice 1042"
}
}
| Field | Description |
|---|---|
tool_id |
Stable id for this action on the widget |
tool_label |
Label from the dashboard |
conversation_id |
Chat thread id when available |
answer_id |
Id for this assistant turn |
page_url |
Page the visitor was on, when known |
user |
{ "id": "…" } when the visitor is identified; otherwise null |
context |
Page context from the embed, or null |
payload |
Values for the fields you configured (keys match your field keys) |
Timeouts are short (a few seconds). Soft rate limit: about 20 calls per minute per widget action.
Datalumo also deduplicates successful deliveries for the same answer_id + tool_id for about a day, so a model retry does not POST twice. Still treat your receiver as idempotent on those ids.
#Verify the signature
Always check the signature before trusting the body. Compute HMAC-SHA256 of {timestamp}.{rawBody} with your signing secret, hex-encode it, and prefix v1=. Compare to X-Datalumo-Signature with a constant-time equals.
Reject requests if the timestamp is too old (for example more than five minutes) to limit replay.
PHP
$timestamp = $_SERVER['HTTP_X_DATALUMO_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_DATALUMO_SIGNATURE'] ?? '';
$rawBody = file_get_contents('php://input');
$expected = 'v1='.hash_hmac('sha256', $timestamp.'.'.$rawBody, $signingSecret);
if (! hash_equals($expected, trim($signature))) {
http_response_code(401);
exit;
}
if (abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($rawBody, true);
Node.js
import crypto from 'node:crypto';
function verify(rawBody, timestamp, signatureHeader, secret) {
const expected =
'v1=' +
crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(String(signatureHeader || '').trim());
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express-style: use the raw body string, not a re-serialised object.
if (!verify(rawBody, req.get('X-Datalumo-Timestamp'), req.get('X-Datalumo-Signature'), secret)) {
return res.status(401).end();
}
Use the exact raw request body bytes. Re-encoding JSON can change whitespace and break the signature.
#Optional reply
Respond with HTTP 2xx. You may return a small JSON body (max 2 KB) so the chatbot can confirm with a reference:
{
"ok": true,
"reference": "TICKET-1842",
"message": "We opened ticket TICKET-1842."
}
| Field | Limits | Description |
|---|---|---|
ok |
bool | Defaults to true if omitted |
reference |
string, max 80 | Short id the assistant can quote |
message |
string, max 280 | Optional visitor-safe confirmation |
Non-JSON or oversized bodies are treated as plain success (no reference). Keep replies visitor-safe; do not return secrets or stack traces.
#Tips
- Prefer a clear label and description so the model knows when to call the action.
- Collect only fields you need. Required fields are enforced before the POST.
- For local testing, point the URL at a tunnel or your staging hook; keep HTTPS in production.