API & Developers
By Conversa Labs
By Conversa Labs
REST API, tokens, webhooks, Custom Scripts SDK and per-module events.
API & Developers overview
Overview Conversa Labs offers a REST API, MCP tools for governed agents, webhooks for real-time events, a Dashboard Apps SDK for secure embedded context, a Custom Scripts SDK to extend interfaces, and per-module events to react to changes. Prerequisites - An account with permission to generate API tokens. - Basic HTTP/JSON knowledge. Step by step 1. Generate an access token in your profile/account settings. 2. Call the REST API authenticating with the token. 3. Configure webhooks to receive events at your endpoint. 4. Use Custom Scripts to customize the dashboard, the portal and the widget. 5. Use the V2 Dashboard Apps SDK for a bounded embedded app, and REST or MCP to administer its native installations. Settings & options - Tokens: per user/account, with access scope. - Webhooks: subscribe to events per inbox/account. - Custom Scripts: JS/CSS injected into the supported surfaces. - Dashboard Apps: a versioned read-only context bridge; configuration writes stay in REST/MCP. Use cases - Sync contacts and conversations with an external CRM. - Trigger automations in your system when something changes on the platform. Tips, limits & best practices - Treat tokens as secrets; never expose them in the front-end. - Respect rate limits and handle errors/retries. Troubleshooting - 401/403: check the token and permissions. - Webhook not arriving: verify the URL, the endpoint status and the signature. See also - Dashboard Apps SDK, REST API and MCP - REST API, tokens and webhooks - Custom Scripts SDK - API reference (Swagger / OpenAPI)
REST API, tokens and webhooks
Overview The REST API lets you read and write platform data (contacts, conversations, messages and more). Authentication uses an access token, and webhooks deliver events to your system in real time. Prerequisites - A valid access token. - An HTTPS endpoint to receive webhooks. Step by step 1. Generate an access token in settings. 2. Include the token in the auth header of your API calls. 3. Make requests to API resources (e.g., list contacts, create a conversation). 4. Configure a webhook with the URL and the events you want. 5. Validate the webhook signature/secret before processing the payload. Settings & options - Token scope: limit access to what's needed. - Webhook events: subscribe only to the events you use. - Signing secret: it is shown once, immediately after the webhook is created. Copy it and store it securely; list and edit responses only confirm that a secret is configured and never reveal it. If it is lost, create a replacement webhook and remove the old destination. - Durable delivery: every account-webhook delivery is recorded before it is queued. Network errors and 5xx responses use backoff retries and remain visible in delivery history. - History and replay: under Settings β Integrations β Webhooks, open a destination's history to search, filter, and sort attempts. An administrator can replay one or several rows; bulk results keep processed and failed IDs separate. - Commerce payload: the default sends canonical identifiers only. Enable Include commerce details on a destination only when the receiver needs normalized buyer, product/variant, and sale items. The opt-in never adds credentials or a raw gateway payload. - Receiver idempotency: use the X-Chatwoot-Delivery header as the idempotency key; a manual replay reuses the same identifier. Event catalog (module groups) All events below are account-level subscriptions (configured per account, on the Webhooks screen). For a module to deliver its events, the module feature must be enabled on the account β the subscription is accepted even with the feature off, but nothing is delivered until you turn it on. - Conversations, messages, contacts, inbox and typing β the support lifecycle. - CRM (deals) β creation, stage, won/lost, priority, value, health, checklist, SLA and close dates. - Catalog and Commerce β products, variants/stock and the Commerce payment cycle. - Orders and revenue β order registration, status, paid, refunded, revenue and affiliates. - Tasks, Calendar and bookings, Payments and Follow-ups β each module's lifecycle. - Contracts and e-signature, Sales Management and Engagement/Lead Score. - Account Brain β risk, insights, department runs and proposed improvements. - Distribution (Launch Groups) β member enrollment, invitation, joining and unit completion. - Growth Social and Ads β comments, ad leads, campaign status and the Click-to-WhatsApp window. - WhatsApp Hub β broadcasts, participant events and group welcome/farewell greetings (Cloud and WazMeow). - WhatsApp calls and Flow responses β call lifecycle and WhatsApp Flow responses. - Team Management β agent status change, breaks and shifts. - FlowBuilder β flow session lifecycle. - Smart Routing β agent assignment. The real fields for each payload are in Per-module events. Use cases - Mirror conversations into a data warehouse. - Notify an external system when a conversation is created or resolved. Tips, limits & best practices - Always verify the webhook signature before acting. - Ensure idempotency with X-Chatwoot-Delivery, including retries and manual replays. - Respect rate limits and use backoff on 429/5xx errors. Troubleshooting - 401/403: invalid token or missing permission. - Duplicate webhook: confirm the receiver deduplicates X-Chatwoot-Delivery. - Failed delivery: open the destination history and inspect its HTTP status/error and next attempt. After fixing the receiver, select the row and confirm replay. See also - Dashboard Apps SDK, REST API and MCP - API & Developers overview - Per-module events - API reference (Swagger / OpenAPI)
API channel: the structured event contract
Overview The API channel is a first-class inbox. Any module of the platform reaches it the same way it reaches WhatsApp or the web widget: it creates a message on a conversation. That message is then POSTed β signed β to your inbox webhook URL. Because every message payload carries the full content_type and content_attributes, structured content (interactive buttons, payment CTAs, catalog cards, calendar events and reactions) reaches your integration natively β you render it. Everything here is additive and backward-compatible: existing integrations keep working and can ignore any field they don't recognize. There are no new mandatory fields. Prerequisites - An API inbox with a configured webhook URL. - The inbox secret to verify the webhook signature (and optionally hmac_token for inbound HMAC). - An access token for the REST API when you send data back. Step by step 1. Create an API inbox and set its webhook URL. 2. Receive events at that URL and verify the signature with the inbox secret. 3. Read content_type + content_attributes on each message payload to render rich content. 4. Send inbound messages, delivery receipts and reactions back through the REST API. 5. (Optional) Restrict which events you receive with webhook_subscriptions. Outbound content types (what the webhook delivers) Each message_created / message_updated payload includes content_type, content, content_attributes and attachments. Beyond plain text, the API inbox may deliver: - input_select β interactive options in content_attributes.items ([{title, value}]). When a channel can't render options, the platform also appends a numbered menu to content. - content_attributes.payment_interactive β a pay action: { body, buttons: [{ type: "cta_url", text, url } | { type: "cta_copy", text, code }] }. The full charge (amount, PIX code, boleto, links) also arrives as content_attributes.payment_charge on the summary message. - content_attributes.catalog_product β a product card: { mode: "single" | "list", products: [{ id, name, price, currency, image_url, ... }] }. - content_attributes.calendar_event β a booking confirmation or reminder: { id, title, starts_at, ends_at, location, meet_url, kind: "confirmation" | "reminder" } (ISO-8601 times). - content_attributes.reactions β an array of { emoji, sender_jid }. The operator is me; a contact is stored under its own identifier. Reactions ride the message_updated event. - Templates β the sent template metadata rides additional_attributes.template_params ({ name, language, category, processed_params }). - Media, location, contacts, stickers β delivered as real attachments (plus content_attributes.media_kind for stickers). Inbound (what you send back) - Incoming message β POST /api/v1/accounts/{account_id}/conversations/{conversation_id}/messages with message_type: "incoming". It accepts content, content_type, content_attributes and attachments, so an interactive reply is symmetric with the outbound contract. - Delivery receipts β PUT/PATCH .../messages/{id} (API inboxes only) with status of sent / delivered / read / failed (and an optional external_error). This flips the message status, which is relayed via message_updated. - Reactions β POST .../messages/{id}/react with emoji (an empty emoji removes it). Pass by=contact to record the contact's reaction (the default records the operator's). No provider is contacted for an API inbox β the reaction is persisted and relayed via message_updated. Campaigns One-off campaigns can target an API inbox. Each audience contact receives a real conversation and an outgoing message, which rides your webhook exactly like any other outbound message. Settings & options - webhook_subscriptions β an optional array on the API channel's additional_attributes (update the inbox via the API with channel[additional_attributes][webhook_subscriptions]). When set, only those events are delivered; when absent or empty, all events are delivered (the default). Event names must be valid platform events (e.g. message_created, message_updated, conversation_created, conversation_status_changed, conversation_typing_on). - hmac_mandatory β reject inbound requests that can't be HMAC-verified. Use cases - Bridge the inbox into a custom app that renders payment CTAs, catalog cards and calendar bookings. - Relay a contact's reaction from your own client back onto the message. - Mirror booking confirmations and reminders into the customer's channel of record. Tips, limits & best practices - Always verify the webhook signature before acting on a payload. - Deduplicate by the delivery id / event; expect retries. - Prefer content_attributes for structured rendering; content is the plain-text fallback. - Subscribe only to the events you use to reduce noise. Troubleshooting - No events arriving: confirm the webhook URL is set and, if you configured webhook_subscriptions, that the event you expect is in the list. - 422 on subscriptions: the list contains an unknown event name β remove it. - Reaction not attributed to the contact: pass by=contact on the react call. See also - REST API, tokens and webhooks - Per-module events - API reference (Swagger / OpenAPI)
API reference (Swagger / OpenAPI)
Overview Conversa Labs publishes a complete API reference in OpenAPI 3.1 format, generated automatically from the product's real routes. It covers every module β conversations, contacts, CRM, catalog, payments, calendar, tasks, follow-ups, sales and gamification, WhatsApp and much more β and stays in sync with the API on every build. There are two ways to view the same reference: - ReDoc (reading) β a navigable documentation, organized by groups and tags, ideal to understand each endpoint's contract: https://app.conversalabs.com.br/swagger - Swagger-UI (interactive) β the same reference with a "Try it out" button to make real API calls right from the browser: https://app.conversalabs.com.br/swagger/ui.html - OpenAPI definition (JSON) β the raw file to import into Postman, Insomnia or to generate SDKs: https://app.conversalabs.com.br/swagger/swagger.json Prerequisites - A valid access token (generated in your profile/account). See the REST API and tokens article. - A modern browser. To test calls, prefer a token from a test environment. Step by step 1. Open the reference at https://app.conversalabs.com.br/swagger (ReDoc) and browse the module groups in the sidebar. 2. Find the endpoint you need (by method and path) and read its parameters, body and responses. 3. To test, open the interactive reference at https://app.conversalabs.com.br/swagger/ui.html. 4. Click Authorize and enter your token in the api_access_token header. 5. Pick an endpoint, click Try it out, fill in the parameters and click Execute. 6. Review the response (status, body) and reuse the generated request example (cURL) in your integration. Settings & options - Auto-generation: the reference is built from introspecting the application's real routes, so new endpoints show up automatically. - Authentication: all authenticated endpoints use the api_access_token header. - Availability: on self-hosted installs, the docs are enabled by the operator via an environment variable (ENABLE_API_DOCS). On hosted Conversa Labs they're already available at the URLs above. Use cases - Quickly discover which endpoints exist for a module (CRM, Payments, Catalog, etc.). - Test a call with your token before writing it in code. - Import the OpenAPI definition into Postman/Insomnia or generate a client SDK. Tips, limits & best practices - Treat the token as a secret β never share it or expose it in the front-end. - For testing, use a minimal-scope token and, ideally, one from a test environment. - Respect rate limits and handle 429/5xx errors with backoff. Troubleshooting - The page doesn't open (404): the docs may be disabled in that environment β the operator enables them with ENABLE_API_DOCS. - 401/403 when testing: the token is invalid or lacks permission; generate a new one and check the scope. - An endpoint is missing: it may require a module/feature that isn't enabled on your account. See also - Dashboard Apps SDK, REST API and MCP - REST API, tokens and webhooks - API & Developers overview - Per-module events
Custom Scripts SDK
Overview Custom Scripts let you inject JavaScript/CSS into specific platform surfaces β the dashboard, the Help Center portal and the chat widget. They're useful for small behavior and style customizations without changing the codebase. Prerequisites - Administrator permission to manage Custom Scripts. - JavaScript/CSS knowledge. Step by step 1. Open the Custom Scripts module. 2. Create a script and choose the surface (dashboard, portal or widget). 3. Set the type (JS or CSS) and when it should run. 4. Use the ctx context provided by the runtime to interact with the surface safely. 5. Implement teardown to clean up what the script created, when applicable. Settings & options - Surface: dashboard / portal / widget. - Type: JS or CSS. - Execution: rules for when the script runs. Use cases - Add a button or notice on a specific screen. - Tweak targeted styles of a surface. - Log usage events for internal analysis. Tips, limits & best practices - Write idempotent scripts with teardown to avoid duplication. - Avoid heavy dependencies; prefer lean code. - Test in a controlled environment before publishing. Troubleshooting - Script doesn't run: confirm the surface and execution rules. - Duplicated behavior: check teardown and idempotency. See also - API & Developers overview - Custom Scripts (Administration)
Dashboard Apps SDK, REST API and MCP
Overview Native Dashboard Apps have three separate integration contracts: - The browser V2 SDK delivers capability-limited context, read-only events, safe frame commands, and optional signed identity for the app's own backend. - The account REST API lets an administrator or custom role with integration_manage create, inspect, update, reorder and delete apps and their installations. - The account MCP server exposes the same installation management contract as four optional tools in Channels & integrations β Dashboard Apps. The SDK authenticates only a short-lived embedded-app identity; it does not expose a generic API proxy. Business writes belong in your backend, authenticated to REST with a least-privilege token. Prerequisites - dashboard_apps_native_surfaces enabled for the account. - An HTTPS Dashboard App hosted on an origin different from the Conversa Labs dashboard for V2 mode. - An account administrator, or custom role with integration_manage, and an api_access_token for REST management. - For MCP, an account MCP profile whose acting user has that permission and whose selected modules include Dashboard Apps. - CSP frame-ancestors configured to allow the exact Conversa Labs origin without a conflicting X-Frame-Options response header. Step by step 1. Connect the V2 browser SDK Import the SDK from the stable, versioned path of your Conversa Labs deployment. It redirects to the current fingerprinted build and exposes named JavaScript module exports. import { connect } from '/dashboard-app-sdk/v2.js'; try { // The host injects cl_* launch parameters, so origin and installation are auto-discovered. // Explicit options remain available for controlled tests. const client = await connect(); const stop = await client.subscribe( ['context.initialized', 'conversation.changed', 'theme.changed'], event => { console.log(event.context_revision, event.data); } ); await client.setHeight(520); document.querySelector('#documentation').addEventListener('click', () => { client.openLink('https://app.example.com/docs'); }); // Later: await stop(); client.disconnect(); } catch (error) { console.error(error.code); } Use the named module imports shown above. They are the stable integration contract for new apps. If your app cannot consume named exports, the same file also exposes window.ConversaLabsDashboardAppSDK with the same functions. Call connect() as soon as your page loads, not behind authentication or a round trip to your own backend. The host starts the handshake when the frame finishes loading and keeps retrying for up to 10 seconds; an app that only starts listening after that window gets handshake_timeout. A static import at the top of a <script type="module"> is the supported shape. A dynamic import() works as long as it is awaited immediately β do not defer loading the SDK until after you render the screen. connect reads cl_dashboard_origin and cl_installation_id from the launch URL, validates the exact dashboard origin, negotiates protocol 2.0, and resolves after the host handshake. The client exposes installationId, capabilities, connected, subscribe, unsubscribe, setHeight, openLink, getIdentityAssertion and disconnect. Core events are context.initialized, conversation.changed, conversation status/assignment/labels, message create/update, contact/current-user updates, locale/theme/permissions changes and installation.changed. Conversation is omnichannel and works for email, WhatsApp, SMS and other inboxes; sidebar context omits conversation and contact data. Always program to the announced capabilities, optional fields and increasing context_revision; resynchronize rather than applying stale data. Secure identity for n8n and other backends Do not send identity or credentials in the iframe query string. In context.initialized, use: - account.id and account.name to identify the account; - current_user.id, current_user.name, current_user.role and current_user.avatar_url to identify the person who opened the app; - installation.id, surface, and sidebar-only sidebar_category and sidebar_icon to identify the installation, location, native category, and selected icon; - on the conversation surface, conversation, contact, permissions and message events for the operational context. The bridge never exposes user/session/CSRF, REST, MCP, or channel-provider credentials. When granted per installation, current_user:email adds current-user email; contact:email and contact:phone add contact data on the conversation surface. Without the capability, the field is omitted from context and events. To let an app backend or n8n webhook verify identity without trusting browser-provided IDs, request a short-lived assertion: const identity = await client.getIdentityAssertion(); await fetch('https://app.example.com/api/dashboard-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(identity), }); The app backend POSTs assertion to the returned introspection_url, optionally with expected_origin: "https://app.example.com". An active response contains account, user, installation, surface and capability claims. The assertion expires after two minutes and every introspection revalidates feature, installation, active user, membership, audience and origin. The public verifier rejects assertions above 16 KiB before signature work. It is not a REST/MCP token and must not be stored or used as a bearer credential. Additional data and writes use the backend's own least-privilege REST credential or restricted MCP profile. 2. Manage installations with REST All paths are account-scoped. The feature-off response is 404 resource_not_found; authenticated members without management permission receive 403 forbidden for management operations. # List visible installations; administrators also receive audience details. curl -sS "https://support.example.com/api/v1/accounts/1/dashboard_app_installations?surface=conversation" \ -H "api_access_token: API_TOKEN" # Create one installation. dashboard_app_id cannot be changed afterwards. curl -sS -X POST "https://support.example.com/api/v1/accounts/1/dashboard_app_installations" \ -H "api_access_token: API_TOKEN" -H "Content-Type: application/json" \ --data '{"dashboard_app_installation":{"dashboard_app_id":42,"surface":"conversation","compatibility_mode":"v2","enabled":true,"position":0,"capabilities":["account:read","current_user:read","current_user:email","permissions:read","appearance:read","installation:read","conversation:read","contact:read","contact:email","contact:phone","messages:read","identity:assertion"],"audience":{"type":"any","roles":["administrator"],"team_ids":[7],"user_ids":[]}}}' # Update or reorder. Send the lock_version returned by the most recent response. curl -sS -X PATCH "https://support.example.com/api/v1/accounts/1/dashboard_app_installations/9" \ -H "api_access_token: API_TOKEN" -H "Content-Type: application/json" \ --data '{"dashboard_app_installation":{"sidebar_category":"applications","sidebar_icon":"rocket","position":1,"lock_version":3}}' # Delete. curl -sS -X DELETE "https://support.example.com/api/v1/accounts/1/dashboard_app_installations/9" \ -H "api_access_token: API_TOKEN" The list returns { "payload": [...], "meta": { "revision": N } }; create, show and update return { "payload": { ... } }; delete returns 204. An installation includes its app id/title/URL, surface, compatibility_mode, enabled, operational visible, position, sidebar-only sidebar_category and sidebar_icon on sidebar resources, capabilities, optional administrator-only audience, transport_security, warnings, lock_version and updated_at. POST /api/v1/accounts/:account_id/dashboard_app_installations/:id/identity_assertion is the authenticated call used by getIdentityAssertion; it enforces the feature, active V2/dual installation, audience and capabilities. Apps should prefer the SDK. Server-to-server validation uses the public POST /dashboard-apps/v2/assertions/introspect endpoint described above. You can also create an app and initial installations atomically by adding an installations array to POST /dashboard_apps. With the feature enabled, omitting the property creates the default conversation + legacy + all installation; an empty array creates only the app. With the feature disabled, sending installations is rejected and omitting it preserves legacy creation. One app can have one installation per surface. 3. Use the account MCP tools Enable Dashboard Apps in the MCP profile. The module stays in the existing channels_integrations group and preserves the existing app-definition tools. It adds: | Tool | Effect | Read-only profile | |---|---|---| | list_dashboard_app_installations | Lists the acting user's visible installations | Available | | create_dashboard_app_installation | Creates an installation | Hidden | | update_dashboard_app_installation | Updates category, icon, position, audience, or bridge | Hidden | | delete_dashboard_app_installation | Deletes an installation | Hidden | MCP uses the same account scope, policy, audience filtering, feature switch and validation errors as REST. The direct REST show route and PUT alias are intentionally not separate MCP tools; list is the canonical read and PATCH is the canonical update/reorder operation. Identity minting is not an MCP tool: it belongs to an embedded human session, while MCP already has its own authenticated principal. Settings & options - Surfaces: conversation or sidebar. - Compatibility modes: legacy, v2 or dual. - Capabilities: required core; conversation/contact/messages only for conversation; optional personal data current_user:email, contact:email, contact:phone; optional signed identity identity:assertion. Omitting them applies the complete useful default for the selected surface; an explicit empty array is rejected with invalid_capabilities. Remove only optional grants and keep every capability required by the selected surface. - Audience: { "type": "all" }, or { "type": "any", "roles": [...], "team_ids": [...], "user_ids": [...] }. Valid roles are agent and administrator; any requires at least one non-empty selector and grants access when any selector matches. - Sidebar category: support, contacts_crm, applications, commercial, productivity, automation, growth, or analytics_config; accepted only for sidebar, with productivity as the default. The applications group sits directly below Contacts & CRM and is omitted when empty. - Sidebar icon: panels_top_left, layout_dashboard, app_window, boxes, briefcase, bot, calendar_days, chart_no_axes_combined, circle_dollar_sign, clipboard_list, database, folder, globe_2, headphones, life_buoy, messages_square, package, rocket, shopping_bag, sparkles, workflow, or wrench; accepted only for sidebar, with panels_top_left as the default. - Position: a zero-based value normalized within a surface and, for the sidebar, within its category. - Concurrency: send lock_version on updates; 409 stale_installation means reload and retry from the current response rather than overwriting another administrator's change. - Stable validation errors include invalid_audience, invalid_capabilities, invalid_sidebar_category, invalid_sidebar_icon, installation_already_exists, invalid_dashboard_app_url, dashboard_app_migration_required, unsafe_same_origin_v2 and resource_not_found. The migration error requires one HTTP(S) frame; query strings and fragments are valid. - SDK limits include 64 KiB messages, 32 subscriptions, 30 commands per 10 seconds and frame height 160β2,000 px. - Handshake deadlines are two independent timers. The dashboard opens the window when the frame loads and re-sends the invitation for up to 10 seconds. The SDK's connect() has its own 10-second limit counted from the call, adjustable with connect({ timeoutMs }). Raising the SDK one does not help: the dashboard stops retrying first. - Lifecycle: the SDK dispatches window events conversalabs.dashboard-apps:bridge.connected, :bridge.paused, :bridge.resumed and :context.resynced. Listen to them to pause work when the tab loses focus and to resynchronize. The disconnected code means every later command will be rejected; reconnect instead of retrying the command. Error codes and the first thing to check | Code | Means | Start with | |---|---|---| | handshake_timeout | The dashboard retried for 10 s and the app never answered | Does the app call connect() on load? Does the loaded bundle expose connect? | | invalid_origin | Configured or bridge origin is invalid | HTTP(S) URL without credentials; in V2 the origin must differ from the dashboard | | frame_blocked | The browser refused the embed | frame-ancestors and X-Frame-Options on the app server | | insecure_transport_blocked | HTTP app inside an HTTPS dashboard | Publish the app over HTTPS | | unsupported_version | App and dashboard share no bridge version | Use the SDK from the same deployment | | unsupported_command | Command outside the allowlist | Use only the announced commands | | invalid_payload | Message outside the contract | Version, sequence, fields and size | | invalid_sequence | Message out of order | Do not reimplement the protocol by hand; use the SDK | | rate_limited | More than 30 commands in 10 s | Lower the frequency and group subscriptions | | disconnected | The bridge closed | Reconnect; pending commands do not come back | | user_denied | The person declined the confirmation | Expected on openLink; do not insist | | capability_denied | Capability not granted | Enable it on the installation; contact data exists only on conversation | | identity_unavailable | The assertion could not be issued | Feature, V2/dual mode, active installation and audience | Use cases - Render live conversation context while keeping CRM mutations in an audited backend. - Provision conversation and sidebar installations from an internal administration service. - Let a read-only AI profile inventory visible apps without granting create, update or delete. - Use MCP write tools in a restricted administrator profile for controlled configuration automation. Tips, limits & best practices - Pin dashboardOrigin to the exact HTTPS origin. Never use *, accept untrusted postMessage origins or implement the wire protocol manually when the SDK is available. - Static query strings and fragments are preserved. In V2/dual the host adds and overwrites only cl_dashboard_origin, cl_installation_id, cl_account_id, cl_surface, cl_protocol and cl_locale; treat every cl_* name as reserved. The host removes static cl_* values before writing those six parameters. Never put tokens, assertions, secrets or personal data in the URL or browser bundle. - Keep REST and MCP credentials server-side, rotate them, and separate read-only inventory profiles from administrative profiles. - Verify audience on the server. Hiding a tab in the iframe is not authorization. - openLink always shows a native host confirmation. The link opens only after the person clicks Open link; an iframe-provided timestamp is never trusted as proof of a gesture. - Handle DashboardAppSDKError.code, disconnects, rate limits and resynchronization explicitly. - Treat transport_security: insecure_http and the insecure_http warning as a migration signal, not approval for production use. Troubleshooting - handshake_timeout: the dashboard re-sent the invitation for 10 seconds and the app never answered. Start with the app, not the network: does it call connect() as soon as its page loads? Does the bundle it loaded expose connect? Only then check the origin and V2/dual mode. A frame that stays blank is a different problem β see frame_blocked. - Dual mode hides a V2 failure: under Dual, agents keep working through the legacy lane and the surface reads ready even when the V2 bridge is dead. The Test dialog reports each bridge separately; to see the raw error, switch the installation to V2 temporarily. - invalid_origin: use an HTTP(S) URL without embedded credentials, and keep V2 on an origin different from the dashboard. - unsupported_version or unsupported_command: use the SDK bundle from the same deployment and act only on announced capabilities. - capability_denied: enable the requested installation capability; contact email/phone exists only on the conversation surface. - identity_unavailable or inactive introspection: request a fresh assertion and verify feature, V2/dual mode, enabled installation, audience, membership and exact app origin. - rate_limited or invalid_payload: reduce command frequency and message/subscription size. - REST/MCP returns 404: check account id, installation id and the native-surfaces feature. Audience filtering can also make a resource invisible to the acting user. - REST/MCP returns 403: the authenticated or acting user is neither an account administrator nor assigned a custom role with integration_manage. - 409 on update: fetch the current installation and retry with its lock_version. - 422 on create/update: inspect the stable error value; check URL, unique app/surface pair, sidebar category, audience selectors and same-origin V2 restrictions. See also - Dashboard Apps: native surfaces, audience and security - REST API, tokens and webhooks - API reference (Swagger / OpenAPI) - Native MCP: connections, server and clients
Per-module events
Overview Many platform modules emit events when something changes β a conversation is created, a payment is confirmed, a deal changes stage, etc. You can react to these events via webhooks or through the internal automation rules. Prerequisites - Configured webhooks (for external consumption) or Automation access (for internal reactions). Step by step 1. Identify the module event you want to consume (e.g., conversation created, payment paid). 2. For external consumption: subscribe to the event in the webhook and handle the payload at your endpoint. 3. For internal reactions: create an automation rule with the matching trigger. 4. Validate and process the payload idempotently. Settings & options - Webhooks: subscription per inbox/account. - Automation: per-event triggers, with conditions and actions. - Payload: contains the event context (ids and relevant data). New per-module events (real payloads) The fields below come from each event's real source β don't invent the format. All are delivered to account webhooks; calls and Flow responses also go to the API channel webhook. Delivery of each group requires the module feature enabled on the account (the subscription works even with the feature off, but nothing is delivered). Team Management β feature: Team Management - wfm_status_changed, wfm_break_started β the agent changed status / started a break. Fields: account_id, account_user_id, user_id, status_key, status_event_id, base_availability, family. - wfm_break_breached β the break exceeded its limit. Fields: the above plus expected_seconds and over_by_seconds. - wfm_break_ended β the agent left a break (any path: manual switch, native dropdown or auto-offline). Fields: the break-start fields plus duration_seconds, within_limit and, when breached, over_by_seconds. - wfm_shift_started, wfm_shift_ended β the agent's planned shift window opened/closed (evaluated server-side every minute). Fields: account_id, account_user_id, user_id, shift_id, schedule_id (when generated from a template), date, starts_at, ends_at. Each lifecycle event fires exactly once per shift. WhatsApp Hub broadcasts β feature: WhatsApp Hub - whatsapp_broadcast_started, whatsapp_broadcast_completed, whatsapp_broadcast_failed β the broadcast changed state. Fields (no message content): account_id, inbox_id, broadcast_id, display_id, title, status, target_type, recipients_count. Works on Cloud and WazMeow (WhatsApp Web) inboxes. WhatsApp group welcome and farewell greetings β feature: WhatsApp Hub - whatsapp_group_member_welcomed, whatsapp_group_member_farewelled β fire only when the welcome/farewell message was actually sent (idempotent, once per participant per dedup window). Fields: account_id, inbox_id, whatsapp_group_id, group_jid, participant_jid, participant_phone, trigger (welcome or farewell). No message content. Ads Manager β feature: Growth Ads or Ads Manager - ads_campaign_status_changed β a mirrored campaign's effective status changed. Fields: account_id, ad_account_id, campaign_id, remote_id, status, effective_status, previous_effective_status. - ctwa_conversation_started β a Click-to-WhatsApp conversation opened the 72h free window. Fields: account_id, conversation_id, contact_id, inbox_id, window_id, expires_at. - ad_window_expiring β that window is near expiry. Same fields. WhatsApp calls and Flow responses β feature: WhatsApp Inbox Suite - whatsapp_call_started, whatsapp_call_ended, whatsapp_call_recording_ready β the call lifecycle. Fields (which are present depends on the stage): provider_call_id, conversation_id, realtime, status, recording_url. Delivered to account and API-channel webhooks. - whatsapp_flow_response_received β a customer completed a WhatsApp Flow. Fields: the flow_response object (id, whatsapp_flow_id, screen, response, contact_id, conversation_id) and the conversation object. Delivered to account and API-channel webhooks. FlowBuilder sessions β feature: Flow Builder - flow_session_started, flow_session_updated, flow_session_completed, flow_session_failed β the flow session started, paused, completed or failed. Fields: id, flow_id, status, current_node_id, conversation_id, account_id. Use cases - Update an external system when a payment is confirmed. - Trigger a follow-up cadence when a deal changes stage. Tips, limits & best practices - Always check the real payload source before mapping fields (don't invent the format). - Ensure idempotency by event identifier. Troubleshooting - Event not arriving: confirm the subscription and the endpoint status. - Unexpected fields: review the actual received payload and adjust the mapping. See also - REST API, tokens and webhooks - Automation rules - API channel: the structured event contract
Native MCP: MCP connections, the Maestro server and clients (Model Context Protocol)
Overview MCP (Model Context Protocol) is the open standard that lets AI assistants (Claude, IDEs, agents) use tools and data from external systems safely. Conversa Labs ships native MCP, and you operate all of it from screens β no integration code required. There are three surfaces: - Account MCP connections (access profiles) β instead of a single account-wide setting, you create N named profiles. The static Bearer connection for each profile has its own module selection, read-only mode, an acting user (an account member) and its own URL + bearer secret. That static connection operates as the profile's acting user and can only narrow what that user may already do. When native OAuth is enabled by the installation, the same profile also has a separate OAuth URL: its tools remain profile-bounded, but each call operates as the account member who approved that OAuth grant β never as the profile's static acting user. - Maestro MCP server β exposes Maestro departments as ask_<department> tools to external clients, under a per-account token. The client never gains more autonomy than the department is already configured for. - Robot as an MCP client β every robot can consume external MCP servers (Linear, Notion, Stripe, GitHub, an internal ERP) as extra tools, under the same approval (HITL), tool-budget and audit rules as any other capability. The static account connection and the Maestro server use the standard Authorization: Bearer <credential> header. The examples below configure that static Bearer path: on the account connection the credential is the profile secret; on the Maestro server it is the Maestro token. They are configuration examples, not proof that every product or version has been validated against this installation. Native account OAuth is a separate, conditional resource with discovery, PKCE and browser consent; do not point an OAuth-capable client at the static URL or assume a client supports this flow before testing it. Connector branding During MCP initialization with protocol 2025-11-25, the server sends the installation name and title, description, website, and brand icons (a primary icon plus light/dark variants). OAuth login and consent screens use the same identity; if the configured image cannot load, they show the installation initials. This metadata is published only when the installation has a safe canonical public HTTPS origin. Without one, the server omits URLs and icons rather than expose an internal or untrusted address. The client decides whether and how to render these fields. Clients that negotiate an older MCP version still receive the technical server name, but that protocol cannot carry icons. In Claude's Custom Connector, use the name entered in its setup: the UI can continue to show that name until it uses the server metadata. Appearance never changes scopes, consent, or credentials. The Account, Platform and Super Admin endpoints build this safe snapshot for every MCP initialization. After a White Label or canonical issuer change, the same versioned snapshot is queued for the direct Maestro endpoint; its next authenticated request uses the newest persisted valid snapshot. A temporary Maestro outage never rolls back the saved branding: the scheduled reconciler retries it. If a provider already created a connector, disconnect and add the server again to force a new initialize request β this does not rotate a token or change permissions. The icon URL must be a first-party asset on that canonical origin, publicly fetchable over HTTPS without a login; an external CDN or another host is not published as MCP brand metadata. A third-party client can cache the icon or elect not to render MCP icon metadata; the server cannot override that client-side behavior. Prerequisites - The MCP feature enabled for the account. Without the flag, the Settings β MCP page simply does not appear in the menu (and the installation's global switch must be on as well). - An account administrator profile to manage MCP connections (the page and creating/editing profiles are admin-only). - For the account connection: no personal token. Each profile (MCP connection) generates its own bearer secret at create/rotate time β that is the client's credential. The profile's acting user must be an account member. Robot (AgentBot) tokens and the personal API token are not the account connection's credential. - For the optional native account OAuth connection: the installation owner must enable native MCP OAuth, apply this release's MCP OAuth database migrations, and configure a valid public HTTPS issuer, in addition to the account MCP feature and global server gate. A feature switch does not replace the schema migration or prove the flow. OAuth has a different URL from the static profile connection; the static profile secret never authenticates it. The person approving the browser consent must be an active member of that account. - The issuer hostname must resolve and route through the public gateway to this same installation. It must serve the MCP OAuth endpoints and their Protected Resource and authorization-server discovery documents; a syntactically valid HTTPS address that routes elsewhere is not sufficient. - Before adopting native OAuth in a third-party client, run its discovery, PKCE and consent flow in a controlled environment. A standards-based endpoint is not evidence that a particular client product, desktop app or hosted AI surface supports the required flow. - For the Maestro server: Maestro provisioned for the account. Without it, the Maestro card is replaced by a hint. - To connect an external MCP server via OAuth: the robot already saved, and the browser allowed to open pop-ups (the provider's consent screen opens in a window). Step by step 1. Open the MCP page Go to Settings β MCP. The screen shows the Account MCP connections card (the access profiles) and the Maestro MCP server card. 2. Create an MCP connection (access profile) On the Account MCP connections card, click New profile. Each profile is an independent connection: 1. Name the profile (for example claude-code-support or bi-read-only) β it helps you tell which client uses which connection. 2. Choose the acting user β an account member. The profile runs as that user: the effective tool list is the intersection of your selection with the Super Admin ceiling and with that user's permissions. In other words, a profile only narrows β it never grants more than the user already has. Only routes rooted at /api/v{N}/accounts/{account} belong to this server: personal profile, MFA, session and notification routes stay outside the account connection even when that user belongs to other accounts. 3. Pick the modules to expose. It is the same picker as always: the whole platform (100+ modules), organized into collapsible areas β Conversations & support, Contacts & CRM, Catalog & commerce, Payments, Tasks & calendar, WhatsApp, Channels & integrations, Automation & AI, Growth & sales, Reports, Team & administration, and Content. Use the search to find a module by name or description, and an area's Select all to turn the whole area on at once. Every row says what the module does and how many tools it adds (the price of the checkbox); the summary under the search adds up the current selection and warns you when it goes past the tool cap β which still applies per profile: A few examples (the full list is on screen, by area): | Example module | What it exposes | |---|---| | Conversations | read, reply, change status, assign, apply labels (on by default) | | Contacts | look up, create and update contacts (on by default) | | Reports | metrics and analytics (read-only) | | Tasks | native tasks module | | CRM Items | deals and pipeline | | Help Center | knowledge base articles | | Catalog | products | | Follow-up | follow-up cadences | | Media Library | media assets | Toolsets that are on out of the box carry the default badge. If the Super Admin did not allow a toolset in the global ceiling, the row is shown disabled with a blocked by super admin badge β you cannot check it. Above the list there are starting-point shortcuts: Essential, Support, Sales and Everything. They only add modules to the current selection β nothing is removed, and no module disappears from the list because a shortcut does not mention it. Use one to start and adjust by hand; modules blocked by the ceiling stay out, as always. 4. Read-only mode: tick the checkbox for the profile to expose only tools that read data (no create, update or delete). 5. ChatGPT deep research compatibility (optional): ChatGPT's deep research connector requires, by name, two tools called search and fetch, and refuses a server that lacks them. Tick the box only on the profiles that connector uses. The two tools only describe what the profile already exposes β they grant no extra access β but they take up room in every other client's tool list. Leave it off for Claude, Claude Code and generic clients. 6. Save the profile. At that moment the bearer secret is shown in plaintext exactly once (shape mcp_...) β copy it immediately. Only the hash and last four characters are stored; the platform never shows the secret again. 7. Copy the connection URL. Its shape is https://YOUR-DOMAIN/api/v1/accounts/ACCOUNT_ID/mcp/c/PUBLIC_ID, where PUBLIC_ID is a non-secret identifier (a slug in the URL) β the bearer secret is what authenticates, not the URL. This is the static Bearer URL. If the installation has enabled native MCP OAuth, the same profile has a different exact resource URL: https://YOUR-DOMAIN/api/v1/accounts/ACCOUNT_ID/mcp/oauth/c/PUBLIC_ID. Do not replace mcp/c with this path in an existing static client and do not paste the profile secret into it. The profile still selects the tool surface, but OAuth calls run as the account member who approves consent. Afterwards, each profile has, on the card, Edit (change name, acting user, modules and read-only mode), Rotate token (generates a new secret and invalidates the previous one right away) and Delete. Disabling or deleting the profile β or the acting user losing account membership β makes the connection stop responding (401/404). 3. Configure the Maestro MCP server 1. On the Maestro MCP server card, look at the status: Token configured or No token yet. 2. Click Generate token (or Rotate token if one already exists). 3. The token plaintext is shown exactly once β copy it immediately. It is never displayed again. Rotating invalidates the previous token right away. 4. Copy the Maestro endpoint URL (it ends in /mcp). 5. An external client with that URL + token sees one ask_<department> tool per active department of the account. If Maestro is not configured for the account, the card is replaced by a hint β provision Maestro first. 4. Connect a real MCP client with a static Bearer This section configures only the static Bearer connection: choose Streamable HTTP, the profile's static URL from step 2 (or Maestro's URL from step 3), and the credential in the Authorization: Bearer ... header. For the account connection that credential is the profile secret (the mcp_... revealed when you create or rotate the profile); for Maestro it is the Maestro token. Client configuration formats change independently of this documentation, so validate the exact client version in a controlled environment. None of the following snippets configures the native OAuth flow. Claude Code β from the terminal: claude mcp add --transport http helpdesk \ https://YOUR-DOMAIN/api/v1/accounts/ACCOUNT_ID/mcp/c/PUBLIC_ID \ --header "Authorization: Bearer PROFILE_SECRET" Or in the project's .mcp.json: { "mcpServers": { "helpdesk": { "type": "http", "url": "https://YOUR-DOMAIN/api/v1/accounts/ACCOUNT_ID/mcp/c/PUBLIC_ID", "headers": { "Authorization": "Bearer PROFILE_SECRET" } } } } Cursor β in ~/.cursor/mcp.json (global) or .cursor/mcp.json (project): { "mcpServers": { "helpdesk": { "url": "https://YOUR-DOMAIN/api/v1/accounts/ACCOUNT_ID/mcp/c/PUBLIC_ID", "headers": { "Authorization": "Bearer PROFILE_SECRET" } } } } VS Code (agent mode) β in .vscode/mcp.json: { "servers": { "helpdesk": { "type": "http", "url": "https://YOUR-DOMAIN/api/v1/accounts/ACCOUNT_ID/mcp/c/PUBLIC_ID", "headers": { "Authorization": "Bearer PROFILE_SECRET" } } } } MCP Inspector β to debug the connection and see the raw tool list: npx @modelcontextprotocol/inspector In the panel: Transport Type = Streamable HTTP, URL = the connection URL, and under Authentication enter the Bearer Token β the profile secret for the account connection, or the Maestro token for the Maestro server (or an Authorization header whose value is Bearer <credential>). Click Connect, then List Tools. Windsurf β same idea: a remote server with the connection URL and headers carrying Authorization: Bearer PROFILE_SECRET (or the Maestro token). In all of them the client runs initialize, then tools/list (lists the exposed tools) and finally tools/call to run one of them. If the client shows fewer tools than expected, that is the display cap: use search_tools to find any other tool (it returns its input schema) and call_tool to run it. Nothing is unreachable β it is merely unlisted. 4a. Connect the native account OAuth resource (conditional) This is a separate connection path, available only when native MCP OAuth is enabled for the installation and the account MCP feature/profile are available. It is not a setting that converts the static Bearer connection, and it is not a fallback for a lost profile secret. 1. Configure the client with the exact OAuth resource URL: https://YOUR-DOMAIN/api/v1/accounts/ACCOUNT_ID/mcp/oauth/c/PUBLIC_ID. 2. Start with that resource's Protected Resource Metadata, not a guessed host URL: https://YOUR-DOMAIN/.well-known/oauth-protected-resource/mcp/oauth/accounts/ACCOUNT_ID/connections/PUBLIC_ID. It advertises the canonical authorization server only while the feature is available. 3. Discover the authorization server at https://YOUR-DOMAIN/.well-known/oauth-authorization-server/oauth/mcp (or use the issuer advertised by the resource metadata). The server supports the public Authorization Code flow with mandatory S256 PKCE, exact resource binding and no client secret. Its discovery metadata declares authorization_response_iss_parameter_supported: validate the canonical issuer returned in the iss parameter on successful and redirectable error callbacks. The signed browser handoff accepts at most 1,024 combined bytes across its persisted authorization values (including client_id, redirect_uri, resource, and state). Keep state short and opaque; do not try to extend the flow with oversized URLs or state values. 4. A public client can use verified public Client ID Metadata (CIMD). Dynamic Client Registration (DCR) is conditional and off by default: it appears in discovery only after a Super Admin enables Dynamic Client Registration in MCP Settings. When off, discovery omits registration_endpoint and registration returns 404 β that is not an OAuth failure. When on, the operator reviews the client in MCP OAuth clients and can disable it; confirmed disablement with a reason and reauthentication is irreversible and revokes the consent/token family. DCR remains a governed test path, not a promise of vendor compatibility. Never send a profile secret, personal API token, Super Admin credential or a client secret to this flow. 5. In the browser, when an active dashboard session or Super Admin Console session belongs to a member of the target account, the server validates and reuses it: you go directly to consent review without entering the password again. Approval remains mandatory. A Super Admin identity enters this flow only when it also has account membership, and the grant/token remains limited to the account, profile, and scopes β no global authority is inherited. If the session is missing, expired, invalid, or belongs to another account, the manual form appears and requires a valid member identity. mcp:read is the read scope; write tools also require mcp:write and a profile that is not read-only. offline_access explicitly requests a rotating refresh token. 6. In Settings β Profile β MCP connections, a member can review and revoke their own approved connections. An account administrator can review all account grants and must select a closed reason when revoking through the administrator flow. Revocation invalidates the connected credential family. 4b. Verify the public resource before connecting an OAuth client Before opening Claude.ai, Claude Code, or another remote client, an operator can verify the public contract without supplying any credential: ruby scripts/mcp_oauth_preflight.rb \ --base-url https://YOUR-DOMAIN \ --account-id ACCOUNT_ID \ --profile-public-id PUBLIC_ID For the separate Super Admin authority, use: ruby scripts/mcp_oauth_preflight.rb \ --base-url https://YOUR-DOMAIN \ --super-admin The verifier accepts only a public HTTPS DNS origin (it rejects IP literals and internal hosts), does not follow redirects, and checks exact Protected Resource Metadata, issuer and audience, discovery, Client ID Metadata (CIMD), PKCE S256, the iss response parameter, scopes, and the 401 challenge from a Bearer-free JSON-RPC tools/list. When discovery advertises DCR, it also checks the exact endpoint; explicit absence because the gate is off is a valid state. Proceed to the controlled test only when it finishes with RESULT: PASS. On a failure, fix the publication before connecting any client. That result confirms server publication; it does not prove a third-party provider is compatible. It does not register a client, open consent, exchange/revoke a token, or change a profile, grant, or credential. The remote installation can append its normal secret-free audit telemetry for discovery and the 401 challenge; that is observability, not an OAuth client created by the verifier. Claude.ai β Custom Connector After an operator enables MCP OAuth and Settings β MCP shows the profile's OAuth connection address, use the Claude connector area for your plan: Use this as a controlled test for the Claude version available to your organization. Do not offer the connection to other people until discovery, consent, tools/list, one read, and a successful revocation have been recorded. Claude's remote connector reaches the server from Claude's cloud infrastructure, including when you use Claude Desktop. The issuer must therefore be public HTTPS and reachable outside a VPN/private network; being reachable only from your own browser is not enough. Remote custom connectors are available in Claude, Cowork, and Claude Desktop on Free, Pro, Max, Team, and Enterprise plans; Free is limited to one custom connector. Also review the current Claude remote-connector guide. 1. On Team/Enterprise, an Owner or Primary Owner goes to Organization settings β Connectors β Add β Custom β Web and registers the remote MCP URL. Each member then goes to Customize β Connectors and chooses Connect. On Free/Pro/Max, use Customize β Connectors β + β Add custom connector in your own account (Free allows one connector). 2. Give it a clear name, such as Conversa Labs β Support, and paste the OAuth connection address into Remote MCP URL. Do not use the static Bearer URL. 3. Advanced settings are optional. On this public-client server, never fill Client Secret or use a Bearer token, profile secret, or Super Admin credential in either field. If the Claude version requires DCR, enable it explicitly through Super Admin first; otherwise it must use CIMD or an already provisioned client. 4. Add the connector, sign in as an active member of the account, and read/approve consent. Claude acts as that person, limited by the profile, that person's own roles, and the scopes. The callback is controlled by the client and is registration information, not a URL you manually replace on the MCP resource. Record the Claude version and the callback actually received during the test; do not put tokens, codes, or callback parameters in evidence. To disconnect, revoke the grant in Settings β Profile β MCP connections (or, as an administrator, from the same account view). Do not rotate a static bearer token: that is a separate connection type and does not disconnect Claude OAuth. Do not label a third-party product as compatible merely because it has an MCP or OAuth option. Validate the exact product, version, redirect handling and consent result end to end before enabling it for users. External connector matrix β controlled test Every row below is unvalidated on this installation. The matrix explains how to prepare a safe attempt; it does not claim vendor support. Always begin with mcp:read, initialize, tools/list, and one read against test data. Test a write only after explicit authorization. | Client | External prerequisites | Controlled-test steps | Revocation | Status | |---|---|---|---|---| | Claude.ai β Custom Connector | Claude plan with custom connectors (Free: one); on Team/Enterprise an Owner/Primary Owner registers it first; public HTTPS issuer reachable from Claude's cloud and passing preflight | Use the exact OAuth URL, keep Advanced settings optional/secret-free, and complete browser/PKCE/consent | Revoke in Settings β Profile β MCP connections and confirm the next call fails | β³ unvalidated | | Claude Desktop | Current Claude Desktop and an account with custom connectors; the server must also be reachable from Claude's cloud, not only the local machine | Configure only the exact OAuth URL and record version, OS, and callback/safe error | Revoke the same grant and test another call | β³ unvalidated | | Codex | Organization/product is eligible for remote MCP and its current connection method is available | Add only the exact OAuth URL through the product's current flow; never use a Bearer secret as a client secret | Revoke the grant and confirm subsequent denial | β³ unvalidated | | ChatGPT / GPT Platform | Eligible plan/workspace, administrator or Developer Mode when applicable, and explicit authorization for the test | Use the connector/app flow actually available, the exact OAuth URL, and browser consent | Revoke the grant and confirm subsequent denial | β³ unvalidated | The Super Admin OAuth resource is a different authority plane and uses a different URL; never replace the Account URL above with /super_admin/mcp/oauth. Follow the Operator article and run a separate test. Record version, plan/entitlement, discovery outcome, consent, tools/list, read, audit event, revocation, and the denied call β without copying tokens, codes, verifiers, or parameterized authorization URLs. Claude Code: local authorization uses a loopback callback with an ephemeral port. The restricted RFC 8252 redirect policy for that callback already has a local matcher and HTTP test; a fixed localhost/127.0.0.1 URL alone does not represent the real port. This is not compatibility proof: the external E2E remains blocked until there is a public HTTPS issuer, Inspector, and evidence from the real client. 5. Give a robot external MCP servers (the robot as a client) 1. Go to Settings β Robots, open the robot and find the MCP servers section. 2. Click Add server and fill in: - Technical name β lowercase letters, numbers and _, starting with a letter (2 to 33 characters), e.g. github. It prefixes that server's tools. - URL of the MCP server (the one the provider publishes). - Transport: Streamable HTTP (recommended) or SSE (legacy). - Timeout in seconds. 3. Authentication: choose the type β none, bearer, header, query, basic or OAuth. On the types that use a secret, enter the secret name β never the value. The value stays in the secrets vault and is never sent to the model. The OAuth type has no secret field at all: the credential is obtained in step 6. 4. Additional headers (optional): key/value pairs, only if the server requires them. 5. Tick Require approval (HITL) so every call to this server's tools goes through human approval. 6. Click Discover: the platform connects to the server right now and lists its tools before you save. Large servers (Linear, Notion) return dozens of tools with long descriptions β use the search and Select all / Clear to curate without scrolling the whole list; the counter shows how many you ticked. You may select a subset (allowlist) β if you select none, every tool of that server is available. 7. Save the robot. Tools are discovered and stored at save time; the agent's turn never performs network discovery. For discovery to resolve already-stored secrets, save the robot first, then click Discover. 6. Connect an external server via OAuth (the Connect button) Some servers (Notion, for example) accept only OAuth β there is no static key. In those cases: 1. On the robot's MCP server, choose Authentication β OAuth. The secret fields disappear β there is nothing to fill in. 2. Save the robot (the connection is stored per robot + server name). 3. Click Connect. A neutral authorization window opens immediately; keep it open. When the platform receives the provider address, that window navigates to the provider's consent screen in an isolated context β the provider cannot access the Studio tab. Sign in and authorize. 4. The window closes itself when it is done and the status flips to Connected (with the expiry and the granted scope, when the provider reports them). If it remains open after the provider confirms, close it so the Studio can check the status. If the browser blocks the window, allow popups for this site and try again. 5. Click Discover and carry on: the server's tools now apply to the robot. 6. Disconnect forgets the credentials of that robot + server pair. To switch provider account: disconnect and connect again. OAuth credentials are stored encrypted on Maestro, tied to the robot + server pair. They are never stored in Conversa Labs, never shown on screens or logs, and never sent to the model. Settings & options - Profiles (MCP connections) for the account: each profile has a name, a static acting user, a toolset selection, read-only mode, its static URL (with a public_id) and its own bearer secret (revealed once, rotatable). The static effective selection is always selection β© Super Admin ceiling β© acting user's permissions. - Native account OAuth (conditional): the OAuth resource is the separate /mcp/oauth/c/PUBLIC_ID URL. It uses the same profile tool surface but the consenting account member, not the static acting user. Its grants are visible/revocable in Settings β Profile β MCP connections; existing grants remain visible for revocation if the server is later disabled. - Read-only mode: per profile, exposes only read tools. - ChatGPT deep research compatibility: per profile, adds the two tools search and fetch that this connector requires by name. They only describe what the profile already exposes. They cost two slots of the tool limit (the server reserves four instead of two when it is on), so leave it off for Claude and generic clients. - Tool cap: there is a per-server cap (512), applied per profile β it caps the listing, not the access. The search_tools (discover, with the schema) and call_tool (run it by name) meta-tools reach everything the profile enabled, still honouring read-only mode, the acting user's permissions and the Super Admin ceiling. - Maestro token: one per account, revealed once, rotatable at any time. - External servers per robot: technical name, URL, transport, timeout, authentication, extra headers, approval (HITL) and a tool allowlist. - Authentication types (external server): | Type | How it works | When to use it | |---|---|---| | none | nothing is sent | genuinely public servers (rare) | | bearer | sends Authorization: Bearer <secret> | the most common case β the secret holds the API key | | header | sends a header whose name you choose, valued from the secret | the provider uses its own scheme | | query | sends the secret as a URL parameter | legacy providers | | basic | username + password (two secrets) | internal servers | | OAuth | Connect button β consent on the provider | the provider only accepts OAuth (Notion) | - Tool budget: the tools of each external server count towards the robot's tool budget β too many servers blow the limit. - SSRF guard: private/internal addresses are blocked when adding an external server. Use cases - Operate the inbox from Claude Code or an IDE: list conversations, reply, resolve. - One profile per client: each IDE, script or partner gets its own connection, with a minimal scope and β for read-only clients β read-only mode on. - Pull the account's reports into an assistant through a read-only profile, run by a restricted acting user. - Revoke a single client: rotate (or delete) that client's profile β every other profile keeps working. - Give a robot access to an internal system (ERP, knowledge base) through an external MCP server, with human approval on sensitive actions. - Let the robot open Linear issues or read a Notion base while it is handling a conversation. - Let the data team ask ask_risk / ask_finance and get the department's findings without opening the dashboard. Tips, limits & best practices - Which authentication to pick per popular external server (most of them answer 401 with an OAuth challenge, yet also accept a plain API key in Authorization: Bearer β leaving it on none is the number-one cause of a failed discovery): | External MCP server | Authentication to pick | |---|---| | Linear | bearer + secret holding the API key | | Stripe | bearer + secret holding the restricted API key | | GitHub | bearer + secret holding a personal access token (PAT) | | Atlassian (Jira/Confluence) | bearer + secret holding the API token | | Sentry | header named Authorization, with a secret whose value is Sentry-Bearer YOUR_TOKEN | | Notion | OAuth (Connect button) β no static key accepted | | Your own internal server | whatever the server requires | - Static secret versus OAuth consent: the static profile secret carries the static acting user's permissions β never more than they already have. A native OAuth grant instead runs as the consenting account member and is bound to its exact OAuth resource. Treat either value as a credential and never swap them between the two URLs. - One profile per client: that way you give each one a minimal scope and can rotate/delete a profile to cut off just that client, without touching the others. - Read-only first: start each profile with read-only mode on and enable writes per toolset as you trust the client. - Less is more: expose only the toolsets the client really uses β huge tool lists degrade the model's choices. - Copy the profile secret immediately: it is never shown again. Lost it? Rotate the profile's token (the old one is invalidated instantly) and update the client that used it. - Secrets by reference: on external MCP servers, never paste the secret value into the field β enter the secret's key. - Copy the Maestro token immediately: it is never shown again. Lost it? Rotate (and update the clients that used the old one). - Approval (HITL): for external servers that write to critical systems, tick Require approval. Troubleshooting - The client shows fewer tools than the modules I enabled in the profile: that is the server's display cap. The rest stay reachable: have the client use search_tools (finds the tool and returns its input schema) and call_tool (runs it by name). To see them in the list, uncheck modules or ask the operator to raise the cap. - The Maestro card shows an internal address (amber warning): the installation never declared Maestro's public address. A client on the same machine connects; an IDE outside the network does not. The operator must set MAESTRO_PUBLIC_BASE_URL to Maestro's public address (and restart the app). - The client connects to Maestro's public address and gets "Invalid Host header" (or a 421): Maestro only accepts the Host the installation declared. It is the same MAESTRO_PUBLIC_BASE_URL β once it is set, the public host is accepted (DNS-rebinding protection stays on). - A connector still shows an old or generic icon: confirm the canonical public HTTPS origin and icon URL load without authentication, save the White Label settings, then reconnect the provider so it runs initialize again. The server preserves the last valid mark while Maestro retries a temporary outage; a provider may still cache or choose not to display its icon metadata. - The MCP page is not in the menu: the MCP feature is off for the account (ask the operator) or your user is not an administrator. - 404 on the connection endpoint: the feature is off for the account, the installation's global switch is disabled, or the URL's PUBLIC_ID does not exist (the profile was deleted or disabled). - 401 on the account connection: the profile secret is wrong or was rotated (the previous one is invalidated instantly), the profile was deleted/disabled, or the acting user lost account membership. Use the secret revealed when you create/rotate the profile β a personal API token and a robot token are not this connection's credential. - A static client tries to log in through OAuth: the static account URL ends in /mcp/c/PUBLIC_ID and accepts only its profile secret in Authorization: Bearer ...; it does not advertise an OAuth server. Keep that client on the static Bearer configuration. - The OAuth resource or discovery returns 404: native OAuth is intentionally dark-shipped. Check the native OAuth enablement, the account MCP feature/profile and the public issuer first, then begin again at the exact Protected Resource Metadata URL. Do not substitute a static secret or a different authority-plane token. - The OAuth URL returns 401: the OAuth bearer is missing, expired, revoked, or belongs to a different resource/authority plane. Read WWW-Authenticate: it identifies the exact Protected Resource Metadata (and includes invalid_token only when a supplied Bearer was rejected). Restart from that metadata; never paste the profile's static secret into this URL. - The OAuth URL returns 403: the bearer is valid, but the call requested a write-capable tool without mcp:write. Request that scope during consent and confirm that the profile is not read-only; mcp:write still does not expose tools outside the profile. - The OAuth URL returns 413: the JSON-RPC request or response exceeds 1 MiB. Reduce the payload or paginate. An oversized request is rejected before execution; a large response is measured only after the operation returns, so do not blindly retry a write β inspect the resource or audit trail first. - An OAuth-capable client still cannot complete consent: record the client and version, redirect URI, discovery result and safe error code in a controlled test. Do not bypass PKCE, resource binding or browser consent; compatibility is only established after an end-to-end proof. - Toolset greyed out with a βblockedβ badge: the Super Admin did not allow that toolset in the global ceiling. Only they can release it. - Lost the profile secret: generate a new one with Rotate token on the profile β the previous one is invalidated instantly. - Lost the Maestro token: generate a new one with Rotate token on the Maestro card β the previous one is invalidated instantly. - Discovery (βDiscoverβ) fails with 401: authentication is almost always still on none. Pick bearer and point the secret at the provider's API key (Sentry uses header; Notion uses OAuth). - Discovery fails for another reason: check the URL, the transport (Streamable HTTP or SSE) and the credentials; save the robot first if auth uses a stored secret; private/internal addresses are refused by the SSRF guard. - The Connect button opens nothing: the browser blocked the pop-up β allow pop-ups for the domain and click again. - Connected, but the tool fails after a while: the provider's authorization expired β Disconnect and Connect again. - An external tool fails at runtime: the error comes back as text to the agent (which self-corrects) and never crashes the turn. - ask_<department> answers βdisabledβ: the department is disabled for the account β enable it on the Account Brain screen. See also - Dashboard Apps SDK, REST API and MCP - API reference (Swagger/OpenAPI) - REST tokens, webhooks and authentication - Bots and the API channel
Scheduled messages API and MCP
Overview The scheduled messages API exposes the same module used by the dashboard. It always operates inside one account and on an existing conversation. MCP tools are generated from this OpenAPI contract and apply the same authorization and validation. Prerequisites - The Scheduled messages account feature enabled. - A user token with access to the account, Inbox and conversation. - A complete account policy. - For creation, a stable 1β128 character X-Idempotency-Key. Step by step 1. Call GET /api/v1/accounts/{account_id}/message_scheduling/settings and verify the effective policy. 2. Send the definition to POST .../message_schedules/preview. Preview neither persists nor sends. 3. Resolve every blocker, then create with POST .../message_schedules and X-Idempotency-Key. 4. Store id and lock_version. Repeating the key with identical content returns the existing schedule; different content returns 409. 5. List occurrences with GET .../message_schedules/{id}/occurrences. 6. For changes and actions, submit the current lock_version and required scope. Settings & options - Kinds: one_time, sequence, recurring_single and recurring_sequence. - Occurrence scopes: this_occurrence, this_and_future and all_future. - send_now requires choosing whether to consume the occurrence or create an immediate copy. - reconcile requires an observed outcome: sent, failed or canceled. - Schedule actions use all_future; resume also supplies the published resume action. - Target, effective sender, capabilities and normalized draft are server-authoritative. In MCP, find tools under Message Schedules. Names derive from operationId; arguments and responses match Swagger. Use cases - An external CRM schedules an idempotent callback after a deal update. - An AI operator uses MCP to inspect blockers and pause a sequence with human confirmation. - A reconciliation process records an uncertain provider outcome without duplicating it. Tips, limits & best practices - Never reuse an idempotency key for a different definition. - On 409, reread the resource; do not guess or increment the local lock. - Do not send campaign_id, audience, segment or contact lists. The contract targets one conversation. - Preview before creation, and treat due-time validation as a second authority. - Do not automatically retry needs_attention; verify the provider effect first. - Use the published OpenAPI document for complete schemas and examples, including multipart drafts. Troubleshooting - 400 invalid_idempotency_key: correct the header format or length. - 401/403: check token, feature, role, Inbox and conversation access. - 409 stale_lock_version: reload the schedule or occurrence and reapply the intention. - 422 preview_blocked: inspect every blocker code and correct policy, channel, sender or content. - MCP tool is missing: verify that the server publishes the current Swagger and refresh the MCP session/catalog. See also - Create and manage scheduled messages - API reference (Swagger/OpenAPI) - MCP server and client
Agent bots and the API channel
Overview Conversa Labs offers two complementary extension points for developers: - API channel: a programmable inbox. Instead of a ready-made channel (WhatsApp, email and so on), you create an inbox of type API and connect your own application. Inbound messages arrive through the REST API and agent replies are delivered to your webhook_url. - Agent bot: a webhook bot (bot_type: webhook). It is assigned to an inbox (of any channel) and receives the conversation/message events at its outgoing_url. The bot processes the event and can reply by calling the API back with its access token. When to use each: - Use the API channel when you need a custom channel that the platform does not offer natively. - Use an agent bot when you want to automate replies (triage, auto-answers, AI) before or alongside human agents, on any inbox. The two can be combined: an API inbox that also has an agent bot assigned to it. Prerequisites - Administrator permission on the account (creating inboxes and agent bots is admin-only). - A reachable HTTPS endpoint to receive webhooks (the channel webhook_url and/or the bot outgoing_url). - A valid access token to authenticate REST API calls. Step by step 1. Create an API inbox: go to Settings β Inboxes β Add β API. Provide a name and, optionally, the webhook_url. After creating it, note the channel identifier that was generated. 2. Send an inbound message: create/open a conversation in that inbox and post the customer's message via the API (POST .../conversations/:id/messages), authenticating with the access token. 3. Configure the channel webhook_url to receive outgoing messages: when an agent replies in the conversation, the payload is delivered to your endpoint. 4. (Optional) Create an agent bot: under Settings β AI agents/Agent bots, provide a name, description and the outgoing_url (bot_type = webhook). Then assign the bot to the inbox so it starts receiving that inbox's events. Settings & options - Bot access token: used by the bot to call the API back (create replies, react, update status). It can be regenerated via reset_access_token. - Bot secret: used to sign/verify the webhook payload sent to the outgoing_url. It can be regenerated via reset_secret. - Channel HMAC verification (hmac_token, hmac_mandatory): validates the contact's identity. With hmac_mandatory enabled, contacts are only accepted with a valid identifier hash, computed from the hmac_token. - Message status update: allowed only on API inboxes (for example, marking delivered/read or recording a send error). - Bot payload (webhook_data): identifies the bot in the event with { id, name, type: "agent_bot" }. Use cases - Custom channel: connect your own application (an internal app, a marketplace, a proprietary channel) as an inbox via the API. - Automated replies: an agent bot that does the initial triage, answers frequent questions and only then hands the conversation over to a human agent. Tips, limits & best practices - Always verify the HMAC signature of the webhook (channel and/or bot secret) before processing the payload. - Ensure idempotency: when creating inbound messages, use a unique source_id to deduplicate redeliveries. - Define a clear handoff point from the bot to the human (for example, assign the conversation to an agent/team and stop replying with the bot). - Treat tokens and secrets as credentials: never expose them in the front-end. Troubleshooting - 401/403: invalid, expired or unauthorized access token. - The bot does not receive events: confirm the outgoing_url is correct and reachable, and that the bot is assigned to the inbox. - Invalid HMAC: the identifier hash does not match the hmac_token; recompute the signature and check the field order/encoding. See also - REST API, tokens and webhooks - Events by module - API channel: the structured event contract
Platform API (multi-account provisioning)
Overview The Platform API operates above accounts: it lets you create and manage accounts, users, user-account memberships and agent bots programmatically. Everything lives under a Platform App β a platform application that the operator creates and that gets its own token. Difference from the account REST API: - The REST API works inside a single account (contacts, conversations, messages) and uses the token of a user/agent in that account. - The Platform API works across accounts (provisioning and lifecycle) and uses the Platform App token. Its endpoints live under the /platform/api/v1 prefix. A Platform App only sees and changes the objects in its permissible list (the accounts, users and bots it created or that were associated with it). Prerequisites - A Platform App created by the operator (Super Admin -> Platform Apps). - The access token of that Platform App (generated together with the app). - The token must travel on the operator backend only β never in the front-end. Step by step 1. The operator creates a Platform App in Super Admin -> Platform Apps. On save, the platform generates an access token bound to the app. 2. Authenticate every call by sending the api_access_token header with the Platform App token. If the token does not belong to a Platform App, the response is 401 Invalid access_token. 3. Create an account: POST /platform/api/v1/accounts api_access_token: <platform-token> Content-Type: application/json { "name": "Acme Co", "locale": "en", "support_email": "support@acme.com" } The new account is automatically added to the app's permissibles. 4. Create a user: POST /platform/api/v1/users { "name": "Mary", "email": "mary@acme.com", "password": "<strong-password>" } The user also enters the app's permissibles. If a user with that e-mail already exists, the platform reuses the existing user. 5. Link user and account (account_user) with a role: POST /platform/api/v1/accounts/<account_id>/account_users { "user_id": <user_id>, "role": "administrator" } Use administrator or agent in the role field. 6. Provision agent bots: POST /platform/api/v1/agent_bots { "name": "Sales Bot", "account_id": <account_id>, "outgoing_url": "https://my-bot/webhook" } Complementary endpoints: GET/PATCH/DELETE /platform/api/v1/accounts/:id, GET :id and DELETE :id for users, GET .../account_users (list), DELETE .../account_users (remove membership), GET :id/login (generates an SSO login link for the user) and POST :id/token. Settings & options - Permissible objects: each Platform App keeps a list of accounts, users and agent bots it can manage. Objects the app creates enter that list automatically. - Token scope: the Platform App token only acts on the app's permissibles β it does not reach accounts/users of other apps nor a single account's internal data (use the account REST API for that). - Account parameters: name, locale, domain, support_email, status, plus features, limits and custom_attributes. - User parameters: name, display_name, email, password and custom_attributes. Use cases - Multi-tenant onboarding / reseller: create one account per customer and seed users in bulk. - SSO-style provisioning: create the user and generate the login link (GET :id/login) to take the user straight to the dashboard with no manual password. - Automated lifecycle: create, update and deactivate accounts and memberships from your own system (for example, when a subscription is completed or canceled). Tips, limits & best practices - Keep the Platform App token on the operator backend only. It has provisioning power β never expose it in the front-end or in client apps. - Work with tight permissions: the app should only touch the objects it created. - Account and user deletion is asynchronous (queued) β a 200 response means the deletion was scheduled, not finished in the same instant. - Handle idempotency: creating a user with an existing e-mail reuses the record; creating the same account-user membership does not duplicate it. Troubleshooting - 401 Invalid access_token: the token does not belong to a Platform App (or is missing/incorrect in the api_access_token header). - 401 Non permissible resource: the object (account/user/bot) is not in the app's permissible list β you are trying to change something the app does not manage. - 404: the supplied id does not exist. See also - REST API, tokens and webhooks - API Reference (Swagger / OpenAPI) - API & Developers overview