Build with BizChat
Send messages to your chatbot from your own server, and receive signed webhooks when conversations, leads and handoffs happen. Available on Starter and above.
https://bizchat.cenodigital.com/api/public/v1/chatSends a message to a chatbot and returns its reply, grounded in that chatbot's knowledge sources. Authenticate with Authorization: Bearer bizk_....
Request body
| chatbot_id | string (UUID) required | The chatbot to talk to. Copy it from the dashboard. |
| message | string required | The visitor's message, up to 10,000 characters. |
| conversation_id | string (UUID) optional | Continue an existing conversation. Omit to start a new one. |
| visitor_id | string optional | Your own ID for the end user, so their conversations stay separate. |
| stream | boolean optional | Set to true to receive the reply as Server-Sent Events. Defaults to false. |
Example
curl https://bizchat.cenodigital.com/api/public/v1/chat \
-H "Authorization: Bearer bizk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"chatbot_id": "YOUR_CHATBOT_ID",
"message": "What are your opening hours?",
"visitor_id": "customer-1234"
}'const res = await fetch("https://bizchat.cenodigital.com/api/public/v1/chat", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BIZCHAT_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
chatbot_id: process.env.BIZCHAT_CHATBOT_ID,
message: "What are your opening hours?",
}),
});
const data = await res.json();
console.log(data.message, data.sources);Response
{
"conversation_id": "6f1c2a9e-...",
"message_id": "b7d0e4c1-...",
"message": "We're open Monday to Friday, 9am to 6pm.",
"status": "bot",
"sources": [ ... ]
}Pass conversation_id back on the next request to keep the context. If a team member has taken over the conversation, status shows that and message is null, because the bot does not reply during a handoff.
Streaming
With "stream": true the response is text/event-stream. Append each token to build the reply; done marks the end.
data: {"type": "token", "content": "We're open"}
data: {"type": "token", "content": " Monday to Friday"}
data: {"type": "sources", "sources": [ ... ]}
data: {"type": "done", "conversation_id": "6f1c2a9e-...", "message_id": "b7d0e4c1-..."}Errors
Webhooks
Add a webhook URL on your chatbot's Webhooks page and choose the events you want. BizChat sends a JSON POST for each event. Use webhooks to connect Zapier, Make, n8n, your CRM or your own server.
Payload
{
"id": "evt_3f9a...",
"event": "lead.captured",
"chatbot_id": "YOUR_CHATBOT_ID",
"timestamp": 1792540800,
"data": { ... }
}Verify the signature
Every delivery includes these headers:
X-BizChat-Event: the event name.X-BizChat-Timestamp: Unix time in seconds when the delivery was signed.X-BizChat-Signature:sha256=followed by the hex HMAC-SHA256 of`${timestamp}.${rawBody}`, keyed with your webhook secret.
import crypto from "node:crypto";
// Use the raw request body exactly as received, before JSON parsing.
export function verifyBizChatWebhook(rawBody, headers, secret) {
const timestamp = headers["x-bizchat-timestamp"];
const signature = headers["x-bizchat-signature"] || "";
// Reject old deliveries to prevent replays (5 minutes here).
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const a = Buffer.from(signature);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Respond with any 2xx status to acknowledge. Compute the signature over the raw body, not a re-serialised copy, or it won't match.