Overview
Integrations connect a chatbot to an external messaging channel or tool server. Supported types:
| Channel | Type value | Description |
|---|---|---|
whatsapp | WhatsApp Business API via Meta | |
| REST API | rest-api | Generic REST webhook for custom integrations |
| Web Widget | web | Embeddable chat widget for websites |
| MCP Server | mcp | Remote tool servers for LLM agents (Model Context Protocol) |
List Integrations
Returns all integrations attached to a chatbot.
GET /api/dev/v1/chatbots/{chatbot_id}/integrations Authorization: Bearer <api-key>GET /api/dev/v1/chatbots/{chatbot_id}/integrations Authorization: Bearer <api-key>
curl -X GET https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations \ -H "Authorization: Bearer <your-api-key>"curl -X GET https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations \ -H "Authorization: Bearer <your-api-key>"
Response - 200 OK
{ "items": [ { "id": "01JST...", "chatbot_id": "01JMXYZ...", "type": "whatsapp", "is_active": true, "created_at": "2026-01-25T11:00:00Z" } ], "total": 1 }{ "items": [ { "id": "01JST...", "chatbot_id": "01JMXYZ...", "type": "whatsapp", "is_active": true, "created_at": "2026-01-25T11:00:00Z" } ], "total": 1 }
Get Integration
GET /api/dev/v1/chatbots/{chatbot_id}/integrations/{integration_id} Authorization: Bearer <api-key>GET /api/dev/v1/chatbots/{chatbot_id}/integrations/{integration_id} Authorization: Bearer <api-key>
curl -X GET https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/<integration-id> \ -H "Authorization: Bearer <your-api-key>"curl -X GET https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/<integration-id> \ -H "Authorization: Bearer <your-api-key>"
Delete Integration
Permanently removes an integration. Users on this channel will no longer be able to reach the chatbot.
DELETE /api/dev/v1/chatbots/{chatbot_id}/integrations/{integration_id} Authorization: Bearer <api-key>DELETE /api/dev/v1/chatbots/{chatbot_id}/integrations/{integration_id} Authorization: Bearer <api-key>
curl -X DELETE https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/<integration-id> \ -H "Authorization: Bearer <your-api-key>"curl -X DELETE https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/<integration-id> \ -H "Authorization: Bearer <your-api-key>"
Response - 204 No Content
WhatsApp Integration
Create WhatsApp Integration
POST /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp Authorization: Bearer <api-key> Content-Type: application/jsonPOST /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp Authorization: Bearer <api-key> Content-Type: application/json
Body
phone_number_idstringrequiredMeta WhatsApp Business phone number ID.
access_tokenstringrequiredPermanent access token from Meta for Business.
verify_tokenstringrequiredToken you set when configuring the Meta webhook.
curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/whatsapp \ -H "Authorization: Bearer <your-api-key>" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "access_token": "EAABwzLixnjYBO...", "verify_token": "my-webhook-verify-token" }'curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/whatsapp \ -H "Authorization: Bearer <your-api-key>" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "access_token": "EAABwzLixnjYBO...", "verify_token": "my-webhook-verify-token" }'
Get WhatsApp Integration
GET /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp Authorization: Bearer <api-key>GET /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp Authorization: Bearer <api-key>
Update WhatsApp Integration
PATCH /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp Authorization: Bearer <api-key> Content-Type: application/jsonPATCH /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp Authorization: Bearer <api-key> Content-Type: application/json
REST API Integration
Use this integration to connect the chatbot to any custom channel via a webhook.
Create REST API Integration
POST /api/dev/v1/chatbots/{chatbot_id}/integrations/rest-api Authorization: Bearer <api-key> Content-Type: application/jsonPOST /api/dev/v1/chatbots/{chatbot_id}/integrations/rest-api Authorization: Bearer <api-key> Content-Type: application/json
Body
webhook_urlstringrequiredThe URL Sarufi will POST outbound messages to.
namestringoptionalFriendly name for this REST integration.
curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/rest-api \ -H "Authorization: Bearer <your-api-key>" \ -H "Content-Type: application/json" \ -d '{ "webhook_url": "https://yourserver.com/chatbot-webhook", "name": "My REST Integration" }'curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/rest-api \ -H "Authorization: Bearer <your-api-key>" \ -H "Content-Type: application/json" \ -d '{ "webhook_url": "https://yourserver.com/chatbot-webhook", "name": "My REST Integration" }'
Get / Update REST API Integration
GET /api/dev/v1/chatbots/{chatbot_id}/integrations/rest-api PATCH /api/dev/v1/chatbots/{chatbot_id}/integrations/rest-api Authorization: Bearer <api-key>GET /api/dev/v1/chatbots/{chatbot_id}/integrations/rest-api PATCH /api/dev/v1/chatbots/{chatbot_id}/integrations/rest-api Authorization: Bearer <api-key>
Web Widget Integration
Embed a real-time chat widget on any website. The widget loads asynchronously, connects over Socket.IO, and supports full conversation history, buttons, lists, and media messages.
Create Web Integration
POST /api/dev/v1/chatbots/{chatbot_id}/integrations/web Authorization: Bearer <api-key> Content-Type: application/jsonPOST /api/dev/v1/chatbots/{chatbot_id}/integrations/web Authorization: Bearer <api-key> Content-Type: application/json
Body
namestringrequiredA friendly name for this web integration.
allowed_domainsarrayoptionalList of domains allowed to load the widget (e.g. ["yoursite.com", "app.yoursite.com"]). Leave empty to allow all domains.
greeting_messagestringoptionalThe first message shown when a visitor opens the widget.
widget_configobjectoptionalAppearance and behaviour overrides - see the Widget Config reference below.
widget_config fields
bot_display_namestringoptionalName shown in the widget header and on bot messages. Defaults to "Assistant".
primary_colorstringoptionalHex color for the bubble, header, and user messages. Defaults to the deployment's brand colour, or "#2a6fdb" when it sets none.
secondary_colorstringoptionalHex color for bot message bubbles. When unset, the widget tints bot bubbles itself.
background_colorstringoptionalHex color for the chat message area background.
bubble_textstringoptionalText shown on the floating bubble button. Defaults to "Need Help?".
bot_avatar_iconstringoptionalIcon key for the bot avatar. One of: "" (Sarufi default), "robot", "headset", "sparkle", "heart", "store", "graduation", "chat".
font_sizenumberoptionalBase font size in pixels, from 12 to 24. Defaults to 14.
positionstringoptionalWidget placement. One of "bottom-right" (default) or "bottom-left".
allowed_pathsarrayoptionalURL path patterns where the widget should appear. Supports exact paths ("/contact") and wildcard prefix matching ("/shop/*"). When null or empty the widget appears on every page.
The widget sizes its own window to the visitor's screen, so there is no width or height to set.
The widget also reads these widget_config fields. The dashboard's web integration form sets all of them except bot_avatar_url.
| Field | Type | What it does |
|---|---|---|
brand_logo_url | string | Your logo, shown on the bubble and, when no avatar is set, in the header |
bot_avatar_url | string | A photo for the header avatar |
enable_suggestions | boolean | Shows AI-generated follow-up questions as chips after each reply. Off by default |
max_suggestions | number | How many chips, from 1 to 5. Defaults to 3 |
suggestion_topics | array | Topics that steer the suggestions, such as ["fees", "admissions"] |
offline_push | boolean | Lets a visitor who has left ask for a browser notification of new replies. On by default |
notification_preview | boolean | Whether that notification quotes the reply. On by default; turn it off if replies can hold private details |
curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/web \ -H "Authorization: Bearer <your-api-key>" \ -H "Content-Type: application/json" \ -d '{ "name": "Main Site Widget", "allowed_domains": ["yoursite.com"], "greeting_message": "Hi! How can I help you today?", "widget_config": { "bot_display_name": "Aria", "primary_color": "#0066ff", "bubble_text": "Need Help?", "position": "bottom-right", "allowed_paths": ["/contact", "/support/*"] } }'curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations/web \ -H "Authorization: Bearer <your-api-key>" \ -H "Content-Type: application/json" \ -d '{ "name": "Main Site Widget", "allowed_domains": ["yoursite.com"], "greeting_message": "Hi! How can I help you today?", "widget_config": { "bot_display_name": "Aria", "primary_color": "#0066ff", "bubble_text": "Need Help?", "position": "bottom-right", "allowed_paths": ["/contact", "/support/*"] } }'
Response - 201 Created
{ "id": "01JUV...", "type": "web", "name": "Main Site Widget", "config": { "widget_key": "wk_AbCdEf...", "allowed_domains": ["yoursite.com"], "widget_config": { "bot_display_name": "Aria", "primary_color": "#0066ff", "position": "bottom-right", "allowed_paths": ["/contact", "/support/*"] } }, "is_active": true }{ "id": "01JUV...", "type": "web", "name": "Main Site Widget", "config": { "widget_key": "wk_AbCdEf...", "allowed_domains": ["yoursite.com"], "widget_config": { "bot_display_name": "Aria", "primary_color": "#0066ff", "position": "bottom-right", "allowed_paths": ["/contact", "/support/*"] } }, "is_active": true }
Get / Update Web Integration
GET /api/dev/v1/chatbots/{chatbot_id}/integrations/web PATCH /api/dev/v1/chatbots/{chatbot_id}/integrations/web Authorization: Bearer <api-key>GET /api/dev/v1/chatbots/{chatbot_id}/integrations/web PATCH /api/dev/v1/chatbots/{chatbot_id}/integrations/web Authorization: Bearer <api-key>
Embedding the Widget
Once your web integration is created you get a widget_key (format: wk_...). Use it to embed the widget on your site with a single <script> tag.
Script Attributes Reference
| Attribute | Required | Description |
|---|---|---|
src | yes | Widget script URL - https://sarufi.io/widget.js |
data-widget-key | yes | Your wk_... key from the integration |
data-server-url | no | Override the backend origin (useful for self-hosted deployments) |
data-sounds | no | "false" starts the widget muted. A visitor's own mute choice is remembered and wins |
data-mode | no | "page" fills the element named by data-container with an open chat that cannot be closed, for a page that is only the chat |
data-container | with data-mode | A CSS selector for the element the chat fills, such as "#chat" |
async | recommended | Loads the script without blocking page rendering |
<!DOCTYPE html> <html lang="en"> <body> <!-- your page content --> <script src="https://sarufi.io/widget.js" data-widget-key="wk_YOUR_WIDGET_KEY" async ></script> </body> </html><!DOCTYPE html> <html lang="en"> <body> <!-- your page content --> <script src="https://sarufi.io/widget.js" data-widget-key="wk_YOUR_WIDGET_KEY" async ></script> </body> </html>
Next.js: lazyOnload strategy
lazyOnload loads the widget after all critical page resources finish. Use it in app/layout.tsx for site-wide embeds, or scope it to a nested layout for specific route groups only.
Angular SSR (Universal)
Wrap the script injection in an isPlatformBrowser(this.platformId) check to prevent it running during server-side rendering.
Shopify Online Store 2.0
On OS2.0 themes you can add a custom snippet under snippets/chat-widget.liquid and render it with {%- render 'chat-widget' -%} to keep theme.liquid tidy.
JavaScript API
The widget exposes window.ChatWidget (and window.SarufiWidget, the same object) once its script has loaded, so call it from code that runs after that, such as a load listener. A call made before the chat connects takes effect when it does.
| Method | What it does |
|---|---|
identify(userId) | Names an anonymous visitor so their conversation resumes. See below |
identify({ token }) | Signs the visitor in with a host token your server mints, for tools that need a signed-in user |
signOut() | Drops the host token; the chat carries on as an anonymous visitor |
onTokenExpired(fn) | Called when the host token expires; return a fresh token, or a promise of one |
setContactDetails(details) | Sends details your team sees beside the conversation. See below |
registerTools(handlers) | Registers the functions the agent may call on your page |
tool(spec) | Declares a tool next to its handler, for definitions signed into the host token |
t | The schema builder tool() takes: t.string(), t.integer(), t.enum([...]) and more |
toolManifest() | The canonical definitions and the digest a host token signs for them |
setTools(names) | Limits the live tools on this page; null lifts the limit |
debug(on) | Console diagnostics for setting up tools; never prints tokens, args or results |
onToolCall(fn) | Called as each tool call starts |
onToolResult(fn) | Called as each tool call settles |
Everything about tools, tokens and their safety rules is in Website tools.
Identify the visitor
Attach a visitor identity so the same person resumes their conversation across devices, sessions, and reconnects.
<script> // Call after your login logic, with the user's email or phone number ChatWidget.identify("alice@acme.com"); </script><script> // Call after your login logic, with the user's email or phone number ChatWidget.identify("alice@acme.com"); </script>
Rules:
- Accepts an email or phone number
- Phone numbers are normalized to E.164 server-side (
0712...,+255712...,255 712 ...all map to the same visitor) - Invalid or synthetic emails are rejected (connection refused), so only pass real addresses
- Control characters, angle brackets, quotes, backticks and backslashes are removed, surrounding spaces trimmed, and the result cut to 100 characters
- When the same
user_idconnects again, the backend resumes their latest conversation - Omit the call entirely for anonymous visitors; behaviour is unchanged
- This names a visitor, it does not verify one. For actions on a signed-in user's behalf use
identify({ token })
Send contact details
Pass extra visitor metadata that agents see in the conversation context panel. Useful for surfacing account info, plan tier, or any business-specific context.
<script> ChatWidget.setContactDetails({ "Name": "Alice Mongi", "Email": "alice@acme.com", "Merchant account name": "Acme Ltd", "Plan": "Pro" }); </script><script> ChatWidget.setContactDetails({ "Name": "Alice Mongi", "Email": "alice@acme.com", "Merchant account name": "Acme Ltd", "Plan": "Pro" }); </script>
Rules:
- Plain
{ key: value }object; labels are up to you - Keys are normalized to snake_case server-side (
"Merchant account name"→merchant_account_name) - Values must be scalars (string, number, boolean); nested objects are dropped
- Max 13 fields, each value max 300 characters
- Details are merged on each reconnect: new keys are added, existing keys are updated, old keys are retained
- Stored once the visitor sends their first message
Full example
<script src="https://sarufi.io/widget.js" data-widget-key="wk_YOUR_WIDGET_KEY" async ></script> <script> // After your app authenticates the user: ChatWidget.identify("alice@acme.com"); ChatWidget.setContactDetails({ "Name": "Alice Mongi", "Merchant account name": "Acme Ltd", "Plan": "Pro" }); </script><script src="https://sarufi.io/widget.js" data-widget-key="wk_YOUR_WIDGET_KEY" async ></script> <script> // After your app authenticates the user: ChatWidget.identify("alice@acme.com"); ChatWidget.setContactDetails({ "Name": "Alice Mongi", "Merchant account name": "Acme Ltd", "Plan": "Pro" }); </script>
Independent features
identify() and setContactDetails() are fully independent. You can use one without the other, or both together. Identity drives session continuity; contact details provide extra context visible to agents.
Path-Based Visibility
By default the widget appears on every page. Use allowed_paths inside widget_config to restrict it to specific pages.
How matching works
| Pattern | Matches |
|---|---|
"/contact" | Exactly /contact |
"/shop/*" | /shop, /shop/, /shop/shoes, /shop/shoes/nike |
"/docs/*" | /docs, /docs/getting-started, /docs/api/auth |
When a visitor navigates to a page not in allowed_paths:
- The widget is hidden (
display: none) - The Socket.IO connection is disconnected - no idle connections on restricted pages
When they navigate back to an allowed page:
- The widget reappears
- The socket reconnects automatically, resuming the conversation from session history
SPA route changes
The widget listens for both popstate (browser back/forward) and intercepts history.pushState / history.replaceState to handle client-side navigation in React, Vue, Angular, and Next.js apps - no extra configuration needed.
Example - show only on support pages
{ "widget_config": { "allowed_paths": ["/support", "/support/*", "/contact", "/help/*"] } }{ "widget_config": { "allowed_paths": ["/support", "/support/*", "/contact", "/help/*"] } }
Example - show on all pages except checkout
There is no "deny list" - define the pages where the widget should appear:
{ "widget_config": { "allowed_paths": ["/", "/products/*", "/about", "/contact"] } }{ "widget_config": { "allowed_paths": ["/", "/products/*", "/about", "/contact"] } }
MCP Server Integration
MCP (Model Context Protocol) integrations give LLM agents access to tools hosted on external servers (order lookups, CRM queries, ticketing systems, internal APIs) without writing custom executor code.
Unlike channel integrations (WhatsApp, REST API, web widget), MCP integrations are many-per-chatbot: each chatbot can have multiple MCP servers registered simultaneously.
Knowledge Base or MCP?
Knowledge Base: ground answers in your own documents via RAG. Best for static content: FAQs, policies, product docs.
MCP Server: let agents call external tools and live APIs. Best for real-time actions: order lookups, CRM queries, booking systems.
You can attach both to the same chatbot. See the Knowledge Bases guide for KB setup.
For the full API reference, including all CRUD endpoints, auth types, connection testing, tool allowlists, and how to wire MCP tools into LLM_AGENT flow actions, see the dedicated guide:
Calling your own backend from a flow?
Use the API_CALL flow action to make outbound HTTP requests to your systems from within any conversation state. See the dedicated guide for full examples, variable substitution, response access, and backend setup.