Start typing to search the documentation.

to navigate·to open

Bot Studio

Client REST API & webhooks

Read a WRNexus bot's data out over HTTP with a bot-scoped API key — list bots, conversations, contacts and submissions — and receive signed submission.created webhooks with the HMAC-SHA256 signature spec and exponential-backoff retry behaviour.

The Bot Studio client API is the read-only, API-key-authenticated surface your own backend calls to pull a bot’s data out of WRNexus, plus the outbound webhooks WRNexus POSTs to your endpoint when a lead is captured. Together they let you sync conversations, contacts and submissions into your CRM and react to new leads in real time.

Two distinct auth models. This page covers the client API (/api/v1/bot/*, authenticated with a wbk_… bot API key). The chat runtime (/api/v1/chat/*, per-conversation token) is documented under Programmatic chat, and the in-dashboard management endpoints (/api/bots/*) use your session cookie and are not intended for external use.

Base URL & authentication

https://app.wrnexus.com

Every client endpoint is gated by a bot-scoped API key. Mint one in Bot Studio under the bot’s API keys panel — the raw key (wbk_…) is shown once at creation and only its hash is stored, so it can never be retrieved or re-displayed. Present it in either header:

Authorization: Bearer wbk_<your_bot_api_key>
X-API-Key: wbk_<your_bot_api_key>

A key is pinned to one bot (and therefore one workspace and organization): it can only ever read its own bot’s data, never another tenant’s. The resolved scope is applied to every query automatically — you never pass a bot, workspace or organization id.

Scopes. Read endpoints require the bots:read scope. A key created with no explicit scopes is unrestricted (receives every scope). A key missing the required scope returns 403.

Rate limits. 300 requests per minute per source IP (checked before the key is resolved, so it also throttles key-guessing) and 120 requests per minute per key. Exceeding either returns 429.

Pagination

List endpoints take limit and offset query parameters:

Param Default Range
limit 50 1200
offset 0 ≥ 0

Every list response echoes the window and the unfiltered total:

{ "conversations": [  ], "count": 50, "total": 412, "limit": 50, "offset": 0 }

Endpoints

Method & path Returns
GET /api/v1/bot The bot this key belongs to.
GET /api/v1/bot/conversations Conversations (paged; status filter).
GET /api/v1/bot/conversations/{id} One conversation.
GET /api/v1/bot/contacts Contacts (paged).
GET /api/v1/bot/contacts/{id} One contact.
GET /api/v1/bot/submissions Lead submissions (paged; formId filter).
GET /api/v1/bot/submissions/{id} One submission.

Conversations

GET /api/v1/bot/conversations?limit=20&status=open
Authorization: Bearer wbk_…
{
  "conversations": [
    {
      "id": "conv_…",
      "botId": "bot_…",
      "channelId": null,
      "contactId": "contact_…",
      "versionId": "ver_…",
      "title": "Do you offer same-day delivery?",
      "status": "open",
      "messageCount": 6,
      "startedAt": "2026-06-24T12:00:00Z",
      "lastMessageAt": "2026-06-24T12:05:00Z",
      "closedAt": null,
      "createdAt": "2026-06-24T12:00:00Z",
      "updatedAt": "2026-06-24T12:05:00Z"
    }
  ],
  "count": 1, "total": 1, "limit": 20, "offset": 0
}

Contacts

GET /api/v1/bot/contacts/{id}
Authorization: Bearer wbk_…
{
  "id": "contact_…",
  "botId": "bot_…",
  "name": "Ajay Ghanwat",
  "email": "ajay@example.com",
  "phone": "+919561417403",
  "channelType": "web",
  "externalId": null,
  "locale": "en",
  "attributes": {},
  "lastSeenAt": "2026-06-24T12:05:00Z",
  "createdAt": "2026-06-24T12:00:00Z",
  "updatedAt": "2026-06-24T12:05:00Z"
}

Submissions

A submission is a completed lead-capture form. fields keeps the ordered, labelled entries; values is the same data flattened to a key → value map for convenience.

GET /api/v1/bot/submissions?formId=form_…&limit=50
Authorization: Bearer wbk_…
{
  "id": "sub_…",
  "botId": "bot_…",
  "formId": "form_…",
  "conversationId": "conv_…",
  "contactId": "contact_…",
  "status": "captured",
  "fields": [
    { "key": "name",    "label": "Full name", "value": "Ajay Ghanwat" },
    { "key": "service", "label": "Service",   "value": "Website development" },
    { "key": "city",    "label": "City",      "value": "Pune" }
  ],
  "values": { "name": "Ajay Ghanwat", "service": "Website development", "city": "Pune" },
  "createdAt": "2026-06-24T12:00:00Z"
}

Webhooks

Instead of polling the submissions endpoint, register a webhook and WRNexus will POST a signed JSON event to your HTTPS endpoint the moment a lead is captured. Webhooks are managed per bot, in Bot Studio under Webhooks (and via the session-authenticated /api/bots/{id}/webhooks management endpoints).

Registering an endpoint

When you create or rotate a webhook, WRNexus returns a signing secret (whsec_…) once — store it, it is never shown again. The secret is held encrypted at rest (AES-256-GCM); WRNexus keeps it only to sign outgoing payloads, and your endpoint keeps its copy to verify them.

Subscribe to specific events or leave the event list empty to receive all events. The only event fired today:

Event Fires when…
submission.created A lead-capture form is submitted.

Delivery headers

Every POST carries:

Header Value
Content-Type application/json
User-Agent wrnexus-bot-webhooks/1
X-WRNexus-Event The event type, e.g. submission.created.
X-WRNexus-Delivery A unique delivery id — stable across retries (idempotency).
X-WRNexus-Signature sha256=<hex> — the HMAC of the raw request body.

Payload envelope

The event is wrapped in a canonical envelope. id equals the X-WRNexus-Delivery header — use it to deduplicate retries.

{
  "id": "9f2c…",
  "event": "submission.created",
  "botId": "bot_…",
  "createdAt": "2026-06-24T12:00:00Z",
  "data": {
    "id": "sub_…",
    "botId": "bot_…",
    "formId": "form_…",
    "conversationId": "conv_…",
    "contactId": "contact_…",
    "status": "captured",
    "fields": [
      { "key": "name",    "label": "Full name", "value": "Ajay Ghanwat" },
      { "key": "service", "label": "Service",   "value": "Website development" }
    ],
    "values": { "name": "Ajay Ghanwat", "service": "Website development" },
    "createdAt": "2026-06-24T12:00:00Z"
  }
}

Verifying the signature

The signature is sha256= followed by the hex-encoded HMAC-SHA256 of the exact raw request body, keyed by your endpoint’s whsec_… secret. Compute the same HMAC over the bytes you received and compare in constant time. Reject any request whose signature does not match.

// Node.js / Express — verify before trusting the body.
import crypto from 'node:crypto';

function verify(rawBody, signatureHeader, secret) {
  const expected =
    'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(signatureHeader || '');
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// rawBody MUST be the unparsed body string/Buffer, not the re-serialised JSON.
app.post('/webhooks/wrnexus', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verify(req.body, req.get('X-WRNexus-Signature'), process.env.WRNEXUS_BOT_WHSEC)) {
    return res.status(401).end();
  }
  const event = JSON.parse(req.body);
  // event.id is the idempotency key — ignore one you have already processed.
  // … enqueue work, then …
  res.status(200).end();
});
# Python — verify before trusting the body.
import hashlib, hmac

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

Retry behaviour

Respond with any 2xx status to acknowledge. WRNexus treats any non-2xx response, a timeout, or a transport failure as a failed attempt and retries with exponential backoff:

  • Up to 5 attempts total — the first immediate, then retries after 1s, 2s, 4s, 8s (the backoff doubles each time, capped at 60s).
  • Retries run in the background, so capturing a submission never blocks on a slow endpoint.
  • The X-WRNexus-Delivery id stays the same across all attempts of one event — make your handler idempotent and deduplicate on it.
  • Each attempt is logged with its HTTP status and error; recent attempts are visible per endpoint in Bot Studio, where you can also send a test event.
  • A short request timeout (10s) protects against a hung receiver.

Design for at-least-once delivery. Because a slow or briefly-failing endpoint is retried, you may legitimately receive the same submission.created event more than once. Always key your processing on the envelope id.

Errors

Errors use the standard WRNexus JSON shape (code + message) with the appropriate HTTP status:

Status When
401 Missing or invalid API key.
403 The key is missing the required bots:read scope.
404 The resource does not exist or is outside the key’s bot scope.
429 Per-IP or per-key rate limit exceeded.