Start typing to search the documentation.

to navigate·to open

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:

  1. Widget snippet — one <script> tag drops a floating chat bubble onto any site.
  2. Hosted page — a WRNexus-hosted URL you link or QR-code to.
  3. 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 active bot 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.colorsettings.brandColorsettings.themeColor (must be valid hex) #4f46e5
logo settings.branding.logosettings.branding.logoUrl → the bot’s avatar URL none
welcome settings.branding.welcomesettings.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 returns 400 EMPTY_MESSAGE; a missing/invalid token returns 401.
  • Channel-aware capture. For web/hosted channels 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.