Bot Studio
Embed & widget SDK
Put a WRNexus bot on any website with the one-line widget snippet, configure it with data-* attributes, control branding from the bot's settings, or drive the chat runtime directly from your own app over the token-authenticated REST endpoints.
There are three ways to surface a published bot in a browser or app, from zero-code to full control:
- Widget snippet — one
<script>tag drops a floating chat bubble onto any site. - Hosted page — a WRNexus-hosted URL you link or QR-code to.
- Programmatic chat — drive the runtime REST endpoints yourself for a custom UI or a native app.
All three sit in front of the same token-authenticated chat runtime, and all three identify the bot by its public bot id — a value that is safe to embed openly. No workspace secret ever ships to the browser.
Publish first. Only an
activebot with a published version is reachable. Copy the snippet from the bot’s Install panel once it is published — an unpublished id renders a graceful “chat unavailable” state.
The widget snippet
Paste this just before </body> (or anywhere in the page — it is async):
<script
src="https://app.wrnexus.com/bots/widget.js"
data-bot-id="BOT_PUBLIC_ID"
async></script>
That is the whole install. widget.js reads its own data-* configuration,
fetches the bot’s public branding from /bots/widget/config, and mounts a
floating launcher in the corner of the page. Opening the launcher reveals an
<iframe> pointing at /bots/embed/{bot_id} — the chat UI served from the
WRNexus (API) origin, so its runtime calls are first-party and your site
never needs any CORS changes.
What ships to the browser
| Value | Secret? | Notes |
|---|---|---|
| The bot’s public id | No | Embedded openly in data-bot-id. Resolves only a published + active bot. |
Per-conversation token (bcs_…) |
No (single-conversation) | Minted by the runtime when a chat starts; authorises exactly one conversation. |
| Workspace / channel secrets | — | Never leave WRNexus. |
data-* attributes
The snippet is configured entirely through attributes on its own <script> tag:
| Attribute | Default | Purpose |
|---|---|---|
data-bot-id |
(required) | The published bot’s public id. Aliases: data-bot-key, data-bot. |
data-position |
right |
Launcher corner — right or left. |
data-color |
the bot’s brand color | Hex accent override (e.g. #0ea5e9). |
data-greeting |
the bot’s welcome line | Teaser-bubble text override. |
data-channel |
web |
Analytics channel tag — web or hosted. |
data-api |
the script’s own origin | Override the API origin (rarely needed). |
data-auto-open |
false |
Set "true" to open the chat panel on page load. |
Example with overrides:
<script
src="https://app.wrnexus.com/bots/widget.js"
data-bot-id="BOT_PUBLIC_ID"
data-position="left"
data-color="#0ea5e9"
data-greeting="Questions about admissions? Ask away."
data-auto-open="true"
async></script>
Anything you do not set falls back to the bot’s published settings, so the
common case is just data-bot-id.
Branding
Branding is sourced from the bot’s own settings, not from the snippet — change it in Bot Studio, re-publish, and every widget, hosted page and embed updates at once. The public config endpoint resolves these values:
| Field | Resolution order | Default |
|---|---|---|
| color | settings.branding.color → settings.brandColor → settings.themeColor (must be valid hex) |
#4f46e5 |
| logo | settings.branding.logo → settings.branding.logoUrl → the bot’s avatar URL |
none |
| welcome | settings.branding.welcome → settings.welcomeMessage → the published version’s brand/tone voice |
derived from bot name + tone |
Only #rgb / #rrggbb hex colors are accepted; any other value (including
attempts at CSS injection) is rejected and the default color is used. All bot
text is sanitised on write, so configuration and embed pages are injection-safe.
You can fetch the resolved branding yourself — it is public, cacheable JSON with permissive CORS:
curl "https://app.wrnexus.com/bots/widget/config?botId=BOT_PUBLIC_ID"
{
"ok": true,
"botId": "BOT_PUBLIC_ID",
"name": "Acme Assistant",
"slug": "acme-assistant",
"locale": "en",
"color": "#0ea5e9",
"logo": "https://cdn.example.com/logo.png",
"welcome": "Hi! I'm Acme Assistant. How can I help you today?",
"apiBase": "https://app.wrnexus.com",
"embedUrl": "https://app.wrnexus.com/bots/embed/BOT_PUBLIC_ID"
}
An unknown, draft or paused bot returns 404 with
{ "ok": false, "code": "BOT_UNAVAILABLE" } and reveals nothing about the
owning workspace.
Hosted page
Every published bot has a shareable, public hosted page — ideal when you have no website or want a link/QR target:
https://bots.wrnexus.com/c/{bot_public_id}
It is the same chat runtime in a branded full-page wrapper, is not login-gated, and degrades to “chat unavailable” for an unknown or unpublished id.
Programmatic chat (REST runtime)
To build your own chat UI (a native app, a custom in-page experience), talk to the runtime directly instead of mounting the iframe. The runtime is public and token-authenticated — perfect for a browser, which cannot hold a secret:
- Start resolves the bot from its public id and mints a single-use per-conversation token. The owning workspace/organization is read from the bot row, never supplied by the caller, so cross-tenant access is impossible.
- Every later call presents that token as
Authorization: Bearer <token>. The token authorises exactly one conversation, so guessing a conversation id gets you nowhere.
Base URL: https://app.wrnexus.com. No API key, no cookie.
1. Start a conversation
POST /api/v1/chat/conversations
Content-Type: application/json
{
"botId": "BOT_PUBLIC_ID",
"channel": "web",
"message": "Do you offer same-day delivery?",
"client": {
"platform": "web",
"pageUrl": "https://example.com/pricing",
"referrer": "https://google.com",
"language": "en"
}
}
message and client are optional. When message is present the bot replies
immediately in the same response. The response returns the conversation, the
conversationToken (store it — it is shown only once), the bot, a greeting,
configured slash shortcuts, the bot’s forms, and the opening reply if you sent
a message:
{
"conversation": { "id": "conv_…", "status": "open", "channel": "web" },
"conversationToken": "bcs_…",
"bot": { "id": "BOT_PUBLIC_ID", "name": "Acme Assistant", "slug": "acme-assistant", "locale": "en", "version": { "id": "ver_…", "version": 3 } },
"greeting": "Hi! I'm Acme Assistant. How can I help you today?",
"shortcuts": [
{ "id": "act_…", "label": "Services", "command": "/services", "description": "See what we offer", "type": "knowledge_query", "displayOrder": 10 }
],
"forms": [ /* published forms the bot can offer */ ],
"reply": { "id": "msg_…", "role": "assistant", "content": "Yes — orders before 2pm ship same day.", "contentType": "text", "metadata": {}, "createdAt": "2026-06-24T12:00:00Z" }
}
2. Post a message
POST /api/v1/chat/conversations/{id}/messages
Authorization: Bearer bcs_…
Content-Type: application/json
{ "message": "What are the delivery charges?" }
{
"userMessage": { "id": "msg_…", "role": "user", "content": "What are the delivery charges?", "contentType": "text", "metadata": {}, "createdAt": "2026-06-24T12:01:00Z" },
"reply": { "id": "msg_…", "role": "assistant", "content": "Delivery is free over ₹999, otherwise ₹49.", "contentType": "text", "metadata": {}, "createdAt": "2026-06-24T12:01:00Z" },
"paused": false
}
Structured actions are optional and backward compatible. Use them when the visitor clicks a slash shortcut or FAQ quick reply; do not rely on command text alone. The runtime validates that the shortcut/FAQ belongs to the bot and is enabled/published before executing it.
{
"message": "/services",
"action": {
"type": "shortcut",
"id": "act_…",
"command": "/services"
}
}
For FAQ quick replies, send the clicked question and FAQ id:
{
"message": "What services do you offer?",
"action": {
"type": "faq",
"id": "faq_…"
}
}
Assistant messages may include optional structured fields. quickReplies is a
list of tappable suggestions (the bot’s published FAQ questions); each one
carries a ready-made action block — echo it back verbatim as the action
field of your next Post a message call when the visitor taps it (see below).
form is null unless the bot is presenting an inline form on this turn.
{
"reply": {
"id": "msg_…",
"role": "assistant",
"content": "Hi! How can I help you today?",
"contentType": "text",
"quickReplies": [
{
"id": "faq_1",
"label": "What services do you offer?",
"type": "faq",
"action": { "type": "faq", "id": "faq_1" }
}
],
"form": null,
"metadata": {}
}
}
Both fields are also promoted to the top level of the assistant message (as
reply.quickReplies and reply.form) and are omitted entirely when empty, so a
plain text answer carries neither — older clients that ignore them keep working.
When the bot returns an inline form, render reply.form.fields in your UI. Field
types are text, email, phone/tel/mobile, textarea, select, date,
time, number and url.
{
"reply": {
"content": "Please share your details for Booking enquiry.",
"contentType": "form",
"form": {
"id": "form_…",
"title": "Booking enquiry",
"fields": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "mobile", "label": "Mobile", "type": "phone", "required": true },
{ "key": "date", "label": "Preferred date", "type": "date", "required": true }
]
}
}
}
3. Submit an inline form
POST /api/v1/chat/conversations/{id}/forms/{formId}/submissions
Authorization: Bearer bcs_…
Content-Type: application/json
{
"answers": {
"name": "Ajay Ghanwat",
"mobile": "+919561417403",
"date": "2026-06-26"
}
}
The runtime validates required fields, stores a lead submission linked to the conversation/contact, appends the form event to the transcript, and returns the confirmation reply:
{
"submission": { "id": "sub_…", "formId": "form_…", "conversationId": "conv_…", "status": "received" },
"userMessage": { "content": "Submitted form: Booking enquiry", "contentType": "form" },
"reply": { "content": "Thanks, we received your request.", "contentType": "text" }
}
When a human agent has taken over the conversation from the inbox, the bot’s
automatic reply is paused: reply is null and paused is true. The user’s
message is still recorded, and the agent answers from the dashboard.
4. Read the transcript / conversation
GET /api/v1/chat/conversations/{id}/messages
Authorization: Bearer bcs_…
GET /api/v1/chat/conversations/{id}
Authorization: Bearer bcs_…
5. End, rate, and reopen a conversation
The widget surfaces an End chat control (and best-effort marks the thread ended when the visitor closes the tab). Ending shows a star-rating + optional message feedback screen, then a thank-you screen whose Start again button reopens the same conversation. The runtime endpoints behind those controls:
POST /api/v1/chat/conversations/{id}/end
Authorization: Bearer bcs_…
Marks the conversation ended (idempotent — a repeat call keeps the first
endedAt/endedBy).
POST /api/v1/chat/conversations/{id}/feedback
Authorization: Bearer bcs_…
Content-Type: application/json
{ "rating": 5, "message": "Super helpful, thanks!" }
Stores a required 1–5 rating plus an optional message (validated +
sanitised server-side). An out-of-range rating returns 400 INVALID_RATING.
POST /api/v1/chat/conversations/{id}/reopen
Authorization: Bearer bcs_…
Flips an ended thread back to open so the visitor can continue where they
left off; any feedback already left is preserved. A thread that has been closed
or archived from the Inbox returns 409 CONVERSATION_CLOSED.
Runtime limits & errors
- Rate limits. Starting a conversation: 30 starts per 5 minutes per IP.
Posting messages: 60 per minute per conversation and 240 per minute per
IP. Exceeding a limit returns
429. - A closed conversation returns
409 CONVERSATION_CLOSED; an empty body returns400 EMPTY_MESSAGE; a missing/invalid token returns401. - Channel-aware capture. For
web/hostedchannels the runtime records the page URL, referrer, language and a browser/device guess from the request. For messaging channels (whatsapp,telegram, …) the request comes from a bridge, not the end user, so only the platform-provided identity is stored — no IP, device or geo is fabricated.
Reading the data back
The widget and runtime are the write side. To pull conversations, contacts and submissions back into your own systems — or to receive a signed webhook whenever a lead is captured — use the client REST API & webhooks.