DEV TOOLS

Support Portal

Add a full client ticket dashboard to any website with a single script tag.

Developer documentation  ·  Plain HTML, Vue, React and site builders

Overview

The Support Portal lets your clients see and raise support tickets from your own website, without logging in to KanBird. You add one script to the site and KanBird provides the rest — the dashboard, the ticket form and the reply thread. Every ticket lands straight in your KanBird workspace.

Dashboard

Total, in-progress, solved and pending counts, the tickets being worked on right now, and the last 10 tickets.

Client ticket list

Every ticket with its Ticket ID (like TIC000123). Search by Ticket ID, filter by date, product and status, and export to Excel.

New ticket form

Name, email, phone (optional), subject, product and message — with required fields clearly marked.

Ticket detail & replies

A status tracker (Received → In Progress → Resolved) and a reply thread between the client and your support team.

How it works

Your websiteLoads one script tag
Support windowFull-screen, hosted by KanBird at app.kanbird.com
KanBird workspaceTickets, replies and activity appear for your team

The script adds a hidden full-screen overlay to your page. When you call KanBirdSupportWidget.open(), the overlay shows a KanBird-hosted window inside an iframe. It lives in a Shadow DOM, so your site's CSS can't break the widget and the widget's CSS can't leak onto your page.

The script deliberately does not add its own launcher button. You choose where it opens from — a menu link, a button, a footer link — and call open() from there.

Get your credentials

Two values connect a website to your KanBird account. Both are created in KanBird under Settings → Support Portal.

  1. Generate the Support Portal keyDo this once per KanBird account. It becomes the data-tenant value. Regenerating it later breaks every embed already live until its data-tenant is updated.
  2. Create a client codeClick Create client code and pick the organization the website belongs to. Each organization gets its own code, which is what ties its tickets to it. This becomes the data-client value.
  3. Copy the embed snippetThe copy button beside a client code gives you a ready-made <script> tag with both values filled in. Send that snippet — and only that — to the website developer.
  4. Assign products to the organizationVisitors must choose a product when they raise a ticket, and they can only choose products assigned to their organization.
ValueScript attributeLooks likeScope
Support Portal keydata-tenantYOUR_TENANT_KEYOne per KanBird account
Client codedata-clientcc_school_x7k2pqOne per organization

In every example on this page, replace YOUR_TENANT_KEY and YOUR_CLIENT_CODE with your own values.

Quick start

Paste this just before the closing </body> tag of your page:

HTML
<script src="https://app.kanbird.com/support-widget-embed.js"
        data-tenant="YOUR_TENANT_KEY"
        data-client="YOUR_CLIENT_CODE"></script>

Then open the widget from anything you like:

HTML
<a href="#" onclick="KanBirdSupportWidget.open(); return false;">Support</a>

That's the whole integration. The guides below show the same thing for common setups, including passing the logged-in visitor's name and email.

Integration guides

Pick the option that matches your website.

Plain HTML

Works on any static or server-rendered page. A complete example:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My website</title>
</head>
<body>

  <nav>
    <a href="#" id="support-link">Support</a>
  </nav>

  <!-- 1. Load the widget -->
  <script src="https://app.kanbird.com/support-widget-embed.js"
          data-tenant="YOUR_TENANT_KEY"
          data-client="YOUR_CLIENT_CODE"></script>

  <!-- 2. Open it from your own link -->
  <script>
    document.getElementById('support-link').addEventListener('click', function (e) {
      e.preventDefault();
      KanBirdSupportWidget.open();
    });
  </script>

</body>
</html>

Pre-fill the logged-in visitor's details

If your site already knows who is signed in, pass their name and email so the New ticket form doesn't ask them to type it again:

JavaScript
var currentUser = { name: 'Karim Ahmed', email: 'karim@example.com' };

document.getElementById('support-link').addEventListener('click', function (e) {
  e.preventDefault();
  KanBirdSupportWidget.open({ name: currentUser.name, email: currentUser.email });
});

Load it as a normal script. Don't use type="module" on the embed tag — the widget reads its own data-tenant and data-client attributes, which browsers don't expose to module scripts.

Vue 3 (Composition API)

In a single-page app the script must be added once, and only in the browser. This small composable loads it on demand and gives you an open() function to call from any component.

1. Add the composable

src/composables/useSupportWidget.js
import { onMounted } from 'vue';

const WIDGET_URL = 'https://app.kanbird.com/support-widget-embed.js';
let loading = null;

// Adds the embed script once, no matter how many components ask for it.
export function loadSupportWidget({ tenant, client }) {
  if (window.KanBirdSupportWidget) return Promise.resolve(window.KanBirdSupportWidget);
  if (loading) return loading;

  loading = new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = WIDGET_URL;
    script.setAttribute('data-tenant', tenant);
    script.setAttribute('data-client', client);
    script.onload = () => {
      if (window.KanBirdSupportWidget) resolve(window.KanBirdSupportWidget);
      else { loading = null; reject(new Error('KanBird support widget did not initialise')); }
    };
    script.onerror = () => {
      loading = null;
      reject(new Error('Could not load the KanBird support widget'));
    };
    document.body.appendChild(script);
  });

  return loading;
}

export function useSupportWidget({ tenant, client }) {
  // Preload after mount so the first click opens instantly.
  onMounted(() => loadSupportWidget({ tenant, client }).catch(console.error));

  async function open(user) {
    const widget = await loadSupportWidget({ tenant, client });
    widget.open(user);
  }

  return { open };
}

2. Use it in any component

src/components/SupportButton.vue
<script setup>
import { useSupportWidget } from '@/composables/useSupportWidget';

// e.g. the user from your auth store — leave it out for anonymous visitors
const props = defineProps({ user: { type: Object, default: null } });

const { open } = useSupportWidget({
  tenant: 'YOUR_TENANT_KEY',
  client: 'YOUR_CLIENT_CODE',
});
</script>

<template>
  <button type="button" @click="open({ name: props.user?.name, email: props.user?.email })">
    Support
  </button>
</template>

Nuxt: the composable only touches window and document inside onMounted and click handlers, so it is safe with server-side rendering.

Vue 2 / Options API

Reuse loadSupportWidget from the file above and call it from a method:

SupportButton.vue
<template>
  <button type="button" @click="openSupport">Support</button>
</template>

<script>
import { loadSupportWidget } from '@/composables/useSupportWidget';

export default {
  props: { user: { type: Object, default: null } },
  methods: {
    async openSupport() {
      const widget = await loadSupportWidget({
        tenant: 'YOUR_TENANT_KEY',
        client: 'YOUR_CLIENT_CODE',
      });
      widget.open({
        name: this.user && this.user.name,
        email: this.user && this.user.email,
      });
    },
  },
};
</script>

React & Next.js

Same idea as Vue: load the script once in the browser, then call open() from a click handler.

1. Add the hook

src/hooks/useSupportWidget.js
import { useCallback, useEffect } from 'react';

const WIDGET_URL = 'https://app.kanbird.com/support-widget-embed.js';
let loading = null;

// Adds the embed script once — safe under React StrictMode's double effects.
function loadSupportWidget({ tenant, client }) {
  if (window.KanBirdSupportWidget) return Promise.resolve(window.KanBirdSupportWidget);
  if (loading) return loading;

  loading = new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = WIDGET_URL;
    script.setAttribute('data-tenant', tenant);
    script.setAttribute('data-client', client);
    script.onload = () => {
      if (window.KanBirdSupportWidget) resolve(window.KanBirdSupportWidget);
      else { loading = null; reject(new Error('KanBird support widget did not initialise')); }
    };
    script.onerror = () => {
      loading = null;
      reject(new Error('Could not load the KanBird support widget'));
    };
    document.body.appendChild(script);
  });

  return loading;
}

export function useSupportWidget({ tenant, client }) {
  // Preload after mount so the first click opens instantly.
  useEffect(() => {
    loadSupportWidget({ tenant, client }).catch(console.error);
  }, [tenant, client]);

  return useCallback(async (user) => {
    const widget = await loadSupportWidget({ tenant, client });
    widget.open(user);
  }, [tenant, client]);
}

2. Use it in a component

src/components/SupportButton.jsx
import { useSupportWidget } from '../hooks/useSupportWidget';

export default function SupportButton({ user }) {
  const openSupport = useSupportWidget({
    tenant: 'YOUR_TENANT_KEY',
    client: 'YOUR_CLIENT_CODE',
  });

  return (
    <button type="button" onClick={() => openSupport({ name: user?.name, email: user?.email })}>
      Support
    </button>
  );
}

Next.js: put SupportButton in a Client Component by adding 'use client'; as the first line of the file. The hook only touches window and document inside effects and click handlers.

WordPress & site builders

Any platform that lets you add custom code before the closing </body> tag can use the standard snippet. Look for a setting called footer code, custom code or scripts:

  • WordPress — add the snippet to the footer with a plugin such as WPCode (Insert Headers and Footers), or edit your theme's footer.php and paste it just before </body>.
  • Shopify — paste it into theme.liquid just before </body>.
  • WebflowProject settings → Custom code → Footer code.
Footer code
<script src="https://app.kanbird.com/support-widget-embed.js"
        data-tenant="YOUR_TENANT_KEY"
        data-client="YOUR_CLIENT_CODE"></script>

Then add a button or link wherever you want the entry point, using a Custom HTML block:

Custom HTML block
<button type="button" onclick="KanBirdSupportWidget.open()">Support</button>

Some builders strip onclick attributes or won't let you edit menu items. If that happens, use the Link or iframe option instead — it needs no JavaScript at all.

JavaScript API

Script attributes

AttributeRequiredDescription
data-tenantYesThe Support Portal key from Settings → Support Portal.
data-clientYesThe client code of the organization this website belongs to.

window.KanBirdSupportWidget

Available as soon as the embed script has loaded.

MethodDescription
open(user?)Opens the full-screen support window. Optionally pass { name, email } to pre-fill the new-ticket form.
close()Closes the window. Visitors can also close it with the × in its top bar.
toggle()Opens the window if it's closed, closes it if it's open.
JavaScript
KanBirdSupportWidget.open();                                   // anonymous visitor
KanBirdSupportWidget.open({ name: 'Karim Ahmed', email: 'karim@example.com' });
KanBirdSupportWidget.close();
KanBirdSupportWidget.toggle();

name and email only pre-fill the new-ticket form. They don't filter what the dashboard shows — see Security & privacy.

How tickets work

The new-ticket form

FieldRequiredNotes
Your nameYesPre-filled when you pass name to open().
Your emailYesUsed to find or create the client's contact in KanBird. Pre-filled when you pass email.
Phone numberNoDigits, spaces and + - ( ) ., 6–30 characters. Saved on the contact if it has no number yet, and always recorded in the ticket's activity.
SubjectYesBecomes the ticket title.
ProductYesMust be a product assigned to the organization in KanBird.
MessageYesBecomes the ticket description.

What happens in KanBird

  • A ticket is created with its own Ticket IDTIC000001, TIC000002, and so on.
  • The ticket's start date is today and its due date is 7 days later.
  • Its status starts as Pending, then moves to In progress and Solved as your team works on it. Clients see the same three stages.
  • It appears in KanBird's Tickets with the source Support Portal, with an activity entry and a notification for the account owner.
  • Client replies join the ticket's message thread in KanBird; your team's replies show up in the widget under Support Team.

Showing tickets your team created

Tickets you create inside KanBird are internal by default. To show one to the client, tick Show this ticket to the client on the Support Portal when creating it, or switch on Visible to client on an existing ticket. If an older ticket doesn't have a Ticket ID yet, it gets one the moment it becomes visible.

The ticket list

  • Search by Ticket ID (a partial ID works), and filter by date range, product and status.
  • Export .xlsx downloads exactly what the current filters show, with Ticket ID, date, title, description, product, status, complete date and time to solve.

Security & privacy

Read this before you embed. The Support Portal key and client code sit in your page's source, so anyone who can view the page can copy them. They identify your organization — they are not passwords. Anyone holding them sees that organization's whole ticket feed, and can open any ticket to read its description and replies.

  • If tickets may contain private information, load the widget only for signed-in users of your site rather than on public pages.
  • Passing name / email is a convenience, not a login. It fills the form; it doesn't limit what the dashboard shows.
  • Only the embed snippet belongs on your website. Never paste anything else from KanBird's settings into a site.
  • You can switch it off at any time. Disable a single client code in Settings → Support Portal to stop that organization's widget, or disable the key to stop all of them.
  • Abuse limits. Each visitor (by IP address) can create up to 10 tickets and send up to 20 replies per minute per client code.

Troubleshooting

What you seeLikely causeFix
KanBirdSupportWidget is not defined The script hasn't loaded yet, was blocked (ad blocker, content-security policy), or is loaded as type="module". Put the embed tag before the code that calls open(), load it as a normal script, and check the browser's Network tab. In Vue/React, use the composable/hook above, which waits for the script.
Console: missing data-tenant or missing data-client The attribute is missing or misspelled, or the script is a module. Copy the snippet again from Settings → Support Portal.
The window says Missing tenant/client code You linked to the widget page without its query parameters. Use the full URL from the Link or iframe guide.
Unknown or inactive client code, or an empty dashboard The client code is mistyped or disabled, the Support Portal key was regenerated, or the portal is disabled. Check Settings → Support Portal, re-enable it if needed, and update data-tenant / data-client.
Please select a product, or No products are assigned to your account yet Product is required, and the organization has no products assigned. Assign one or more products to the organization in KanBird.
Too many requests. Please try again in a minute. The per-minute limit was reached (10 new tickets or 20 replies). Wait a minute and retry.
The window opens blank, or the browser refuses to load it Your site's Content-Security-Policy blocks KanBird. Allow https://app.kanbird.com in both script-src and frame-src.
A ticket your team created isn't in the client's list It's still internal. Switch on Visible to client for that ticket in KanBird.
Two support windows, or the script loads twice The snippet is being added more than once (common in single-page apps). Add it once — the composable/hook above already guards against duplicates.

Still stuck? Email info@kanbird.com and include your page URL and any console errors.