API reference

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.

Keys

BizChat uses two kinds of key:

  • bizpk_... publishable key, used by the website widget. It is safe in a web page. Find it on your chatbot's Widget page.
  • bizk_... secret key, used by the server API. Create one on your chatbot's API page in the dashboard. A key can be limited to one chatbot.

Keep secret keys on your server. Never put them in browser code, mobile apps or public repositories.

Endpoint
POSThttps://bizchat.cenodigital.com/api/public/v1/chat

Sends a message to a chatbot and returns its reply, grounded in that chatbot's knowledge sources. Authenticate with Authorization: Bearer bizk_....

Request body

chatbot_idstring (UUID)
required
The chatbot to talk to. Copy it from the dashboard.
messagestring
required
The visitor's message, up to 10,000 characters.
conversation_idstring (UUID)
optional
Continue an existing conversation. Omit to start a new one.
visitor_idstring
optional
Your own ID for the end user, so their conversations stay separate.
streamboolean
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

  • 401Missing or invalid secret key.
  • 402Your plan doesn't include API access (Starter and above).
  • 403The key isn't allowed to chat, or is restricted to a different chatbot.
  • 404Chatbot not found or inactive.
  • 429Too many requests. Each key can make up to 120 requests a minute.
Webhooks

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.

conversation.startedA visitor starts a new conversation.
message.sentThe chatbot sends a reply.
lead.capturedA visitor submits their name, email or phone number.
handoff.requestedA visitor asks to talk to a person.

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.

Questions about the API?

We're happy to help with your integration.