Resources

Integrations

Connect chatbots to WhatsApp, REST API, and web widget channels.

Overview

Integrations connect a chatbot to an external messaging channel. Three channel types are supported:

ChannelType valueDescription
WhatsAppwhatsappWhatsApp Business API via Meta
REST APIrest-apiGeneric REST webhook for custom integrations
Web WidgetwebEmbeddable chat widget for websites

List Integrations

Returns all integrations attached to a chatbot.

http
GET /api/dev/v1/chatbots/{chatbot_id}/integrations
Authorization: Bearer <api-key>
bash
curl -X GET https://beta-api.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/integrations \
  -H "Authorization: Bearer <your-api-key>"

Response — 200 OK

json
{
  "items": [
    {
      "id": "01JST...",
      "chatbot_id": "01JMXYZ...",
      "type": "whatsapp",
      "is_active": true,
      "created_at": "2026-01-25T11:00:00Z"
    }
  ],
  "total": 1
}

Get Integration

http
GET /api/dev/v1/chatbots/{chatbot_id}/integrations/{integration_id}
Authorization: Bearer <api-key>
bash
curl -X GET https://beta-api.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.

http
DELETE /api/dev/v1/chatbots/{chatbot_id}/integrations/{integration_id}
Authorization: Bearer <api-key>
bash
curl -X DELETE https://beta-api.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

http
POST /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp
Authorization: Bearer <api-key>
Content-Type: application/json

Body

phone_number_idstringrequired

Meta WhatsApp Business phone number ID.

access_tokenstringrequired

Permanent access token from Meta for Business.

verify_tokenstringrequired

Token you set when configuring the Meta webhook.

bash
curl -X POST https://beta-api.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

http
GET /api/dev/v1/chatbots/{chatbot_id}/integrations/whatsapp
Authorization: Bearer <api-key>

Update WhatsApp Integration

http
PATCH /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

http
POST /api/dev/v1/chatbots/{chatbot_id}/integrations/rest-api
Authorization: Bearer <api-key>
Content-Type: application/json

Body

webhook_urlstringrequired

The URL Sarufi will POST outbound messages to.

namestringoptional

Friendly name for this REST integration.

bash
curl -X POST https://beta-api.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

http
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

http
POST /api/dev/v1/chatbots/{chatbot_id}/integrations/web
Authorization: Bearer <api-key>
Content-Type: application/json

Body

namestringrequired

A friendly name for this web integration.

allowed_domainsarrayoptional

List of domains allowed to load the widget (e.g. ["yoursite.com", "app.yoursite.com"]). Leave empty to allow all domains.

greeting_messagestringoptional

The first message shown when a visitor opens the widget.

widget_configobjectoptional

Appearance and behaviour overrides — see the Widget Config reference below.

widget_config fields

bot_display_namestringoptional

Name shown in the widget header and on bot messages. Defaults to "Assistant".

primary_colorstringoptional

Hex color for the bubble, header, and user messages. Defaults to "#0066ff".

secondary_colorstringoptional

Hex color for bot message bubbles. Defaults to "#f0f4ff".

background_colorstringoptional

Hex color for the chat message area background.

bubble_textstringoptional

Text shown on the floating bubble button. Defaults to "Need Help?".

bot_avatar_iconstringoptional

Icon key for the bot avatar. One of: "" (Sarufi default), "robot", "headset", "sparkle", "heart", "store", "graduation", "chat".

font_sizenumberoptional

Base font size in pixels (12–24). Defaults to 13.5.

positionstringoptional

Widget placement. One of "bottom-right" (default) or "bottom-left".

widthnumberoptional

Chat window width in pixels (280–600). Defaults to 380.

heightnumberoptional

Chat window height in pixels (400–800). Defaults to 600.

allowed_pathsarrayoptional

URL 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.

bash
curl -X POST https://beta-api.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

json
{
  "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

http
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

AttributeRequiredDescription
srcyesWidget script URL — https://beta.sarufi.io/widget.js
data-widget-keyyesYour wk_... key from the integration
data-server-urlnoOverride the backend origin (useful for self-hosted deployments)
asyncrecommendedLoads the script without blocking page rendering

Plain HTML

Paste the snippet just before the closing </body> tag. This ensures the widget loads after the page content is ready.

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My Site</title>
  </head>
  <body>
    <!-- your page content -->

    <script
      src="https://beta.sarufi.io/widget.js"
      data-widget-key="wk_YOUR_WIDGET_KEY"
      async
    ></script>
  </body>
</html>

Placement matters

Placing the script before </body> (rather than in <head>) means it never blocks your page from rendering. The async attribute ensures parallel loading.


Next.js (App Router)

Use the built-in next/script component with strategy="lazyOnload". This loads the widget during browser idle time — after all critical resources — so it never impacts your Core Web Vitals.

Site-wide (every page) — add to app/layout.tsx:

tsx
// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://beta.sarufi.io/widget.js"
          data-widget-key="wk_YOUR_WIDGET_KEY"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

Specific route group only — add to a nested layout:

tsx
// app/(marketing)/layout.tsx
import Script from "next/script";

export default function MarketingLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <Script
        src="https://beta.sarufi.io/widget.js"
        data-widget-key="wk_YOUR_WIDGET_KEY"
        strategy="lazyOnload"
      />
    </>
  );
}

Why lazyOnload?

lazyOnload loads the script after the entire page — including images and fonts — has finished loading. This is the recommended strategy for chat widgets and other non-critical UI.

document.currentScript in Next.js

Next.js injects <Script> tags asynchronously, so document.currentScript will be null at the time the widget executes. The Sarufi widget handles this automatically via a querySelector fallback — no extra configuration is needed.


React (Vite / Create React App)

Option A — index.html (recommended for all pages)

Add the script tag to public/index.html (CRA) or index.html (Vite) before </body>:

html
<!-- public/index.html (CRA) or index.html (Vite) -->
<body>
  <div id="root"></div>
  <script
    src="https://beta.sarufi.io/widget.js"
    data-widget-key="wk_YOUR_WIDGET_KEY"
    async
  ></script>
</body>

Option B — useEffect in a component (programmatic control)

Use this when you want to conditionally load the widget based on application state (e.g. only after a user logs in).

tsx
// src/components/SarufiWidget.tsx
import { useEffect } from "react";

export function SarufiWidget() {
  useEffect(() => {
    if (document.querySelector('script[data-widget-key]')) return; // already loaded

    const script = document.createElement("script");
    script.src = "https://beta.sarufi.io/widget.js";
    script.setAttribute("data-widget-key", "wk_YOUR_WIDGET_KEY");
    script.async = true;
    document.body.appendChild(script);
  }, []);

  return null;
}

Then mount it once at the top of your app:

tsx
// src/App.tsx
import { SarufiWidget } from "./components/SarufiWidget";

export default function App() {
  return (
    <>
      <Router>
        {/* your routes */}
      </Router>
      <SarufiWidget />
    </>
  );
}

Vue 3 (Vite)

Option A — index.html

html
<!-- index.html -->
<body>
  <div id="app"></div>
  <script
    src="https://beta.sarufi.io/widget.js"
    data-widget-key="wk_YOUR_WIDGET_KEY"
    async
  ></script>
</body>

Option B — composable for programmatic loading

ts
// src/composables/useSarufiWidget.ts
import { onMounted } from "vue";

export function useSarufiWidget(widgetKey: string) {
  onMounted(() => {
    if (document.querySelector('script[data-widget-key]')) return;

    const script = document.createElement("script");
    script.src = "https://beta.sarufi.io/widget.js";
    script.setAttribute("data-widget-key", widgetKey);
    script.async = true;
    document.body.appendChild(script);
  });
}

Use it in your root App.vue:

vue
<!-- src/App.vue -->
<script setup lang="ts">
import { useSarufiWidget } from "@/composables/useSarufiWidget";

useSarufiWidget("wk_YOUR_WIDGET_KEY");
</script>

<template>
  <RouterView />
</template>

Angular

Option A — index.html (simplest)

html
<!-- src/index.html -->
<body>
  <app-root></app-root>
  <script
    src="https://beta.sarufi.io/widget.js"
    data-widget-key="wk_YOUR_WIDGET_KEY"
    async
  ></script>
</body>

Option B — AppComponent with DOCUMENT injection

ts
// src/app/app.component.ts
import { Component, OnInit, Inject } from "@angular/core";
import { DOCUMENT } from "@angular/common";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
})
export class AppComponent implements OnInit {
  constructor(@Inject(DOCUMENT) private document: Document) {}

  ngOnInit(): void {
    if (this.document.querySelector('script[data-widget-key]')) return;

    const script = this.document.createElement("script");
    script.src = "https://beta.sarufi.io/widget.js";
    script.setAttribute("data-widget-key", "wk_YOUR_WIDGET_KEY");
    script.async = true;
    this.document.body.appendChild(script);
  }
}

Angular SSR (Universal)

If you use Angular Universal, wrap the script injection in an isPlatformBrowser check to avoid running it during server-side rendering.

ts
import { isPlatformBrowser } from "@angular/common";
import { PLATFORM_ID } from "@angular/core";

constructor(
  @Inject(DOCUMENT) private document: Document,
  @Inject(PLATFORM_ID) private platformId: object
) {}

ngOnInit(): void {
  if (!isPlatformBrowser(this.platformId)) return;
  // ... script injection
}

WordPress

Option A — Theme functions.php (recommended)

Add this to your active theme's functions.php:

php
<?php
function sarufi_widget_script() {
    echo '<script src="https://beta.sarufi.io/widget.js"
               data-widget-key="wk_YOUR_WIDGET_KEY"
               async></script>';
}
add_action( 'wp_footer', 'sarufi_widget_script' );

wp_footer fires just before </body>, which is the correct placement.

Option B — Insert Headers and Footers plugin

If you prefer not to edit theme files, install the Insert Headers and Footers plugin and paste the snippet into the Footer Scripts field.

html
<script
  src="https://beta.sarufi.io/widget.js"
  data-widget-key="wk_YOUR_WIDGET_KEY"
  async
></script>

Shopify

Option A — Theme editor (no code)

  1. In your Shopify admin go to Online Store → Themes → Customize.
  2. Click App embeds in the left panel.
  3. If the widget is not listed as an app embed, use Option B below.

Option B — Theme code editor

  1. Go to Online Store → Themes → Edit code.
  2. Open layout/theme.liquid.
  3. Paste the snippet just before </body>:
liquid
<!-- layout/theme.liquid -->
<script
  src="https://beta.sarufi.io/widget.js"
  data-widget-key="wk_YOUR_WIDGET_KEY"
  async
></script>

</body>
</html>

Shopify Online Store 2.0

On OS2.0 themes you can also add a custom snippet under snippets/sarufi-widget.liquid and render it from theme.liquid with {%- render 'sarufi-widget' -%} to keep your layout file tidy.


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

PatternMatches
"/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

json
{
  "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:

json
{
  "widget_config": {
    "allowed_paths": ["/", "/products/*", "/about", "/contact"]
  }
}