Introduction

Support Frix is a framework-agnostic support widget. You embed a single custom element, and your users get a polished button that opens a modal where they pick a category and send a message. Tickets are saved to your database and mirrored to a Telegram channel, and your replies flow back into the widget in real time.

The widget renders inside the Shadow DOM, so it is fully isolated: your CSS can't break it and its CSS can't leak into your app.

New here? Jump to Installation for the 30-second setup.

Installation

Load the bundle once, then place the element anywhere in your markup.

snippet
<script type="module"
  src="https://support.frix.me/widget/support-frix-widget.js"></script>

<support-frix-widget
  api-key="YOUR_API_KEY"
  auth-mode="both"
></support-frix-widget>

Get YOUR_API_KEY from the dashboard Platforms. Self-hosting? Point the script at your own /widget/ path and set api-url accordingly.

Configuration

Everything is a plain HTML attribute. No bundler, no config files.

AttributeDefaultNotes
api-keyRequired. Platform key from the dashboard.
api-urlhttps://support.frix.me/apiBase URL of the API.
auth-modebothemail · telegram · both
primary-color#6366f1Accent color.
btn-radius9999pxLauncher corner radius.
btn-positionbottom-rightbottom-right · bottom-left
btn-labelSupportLauncher button text.
titleHow can we help?Modal heading.
themelightlight · dark · auto

Identity modes

The auth-mode attribute decides how a user identifies before sending a ticket.

  • email — the user leaves a name and email. Lowest friction; ideal for public marketing sites.
  • telegram — the user enters their Telegram, receives a 6-digit code from the bot, and confirms. Replies then reach them directly on Telegram.
  • both — show both options and let the user pick. A sensible default.

Receiving replies

However a user identified, your answer reaches them:

  • Inside the widget. On submit, the widget stores a private publicToken in localStorage and polls /api/tickets/:token. When you reply, the conversation updates automatically — no login required.
  • On Telegram. If the user verified via Telegram, the bot also DMs them your answer with the platform name as a header.
The widget remembers the user's open ticket on that device, so they can reopen the button later and continue the conversation.

Framework guides

Because it's a standard web component, integration is nearly identical everywhere — only the "allow this custom tag" detail differs.

// React treats unknown tags as custom elements automatically.
import { useEffect } from "react";

export function SupportButton() {
  useEffect(() => {
    const s = document.createElement("script");
    s.type = "module";
    s.src = "https://support.frix.me/widget/support-frix-widget.js";
    document.body.appendChild(s);
  }, []);

  return (
    // @ts-expect-error — custom element has no JSX types
    <support-frix-widget api-key="YOUR_API_KEY" auth-mode="both" />
  );
}

Styling & theming

Match your brand with primary-color, btn-radius, btn-position and theme. Because the widget lives in the Shadow DOM, these attributes are the supported styling surface — your global stylesheet won't reach inside.

snippet
<support-frix-widget
  api-key="YOUR_API_KEY"
  primary-color="#0ea5e9"
  btn-radius="14px"
  btn-position="bottom-left"
  btn-label="Help"
  theme="dark"
></support-frix-widget>

Create the bot

You only do this once for your whole installation.

  1. Open @BotFather in Telegram and send /newbot.
  2. Choose a name and a username (e.g. SuppFrixBot).
  3. Copy the token BotFather gives you and set it as TELEGRAM_BOT_TOKEN in your API .env.

Create the channel & add the bot

This is the channel every ticket is mirrored into. Do it from your own Telegram account — a bot can't create channels.

  1. In Telegram: New Channel → name it (e.g. "Frix Support"). Private is fine.
  2. Open the channel → Administrators Add Admin.
  3. Search @SuppFrixBot and add it. Grant at least Post Messages.
  4. Get the channel ID: forward any channel post to @getidsbot, or temporarily make the channel public and read it from the API. Channel IDs look like -1001234567890.
Keep the channel private and the bot's admin rights minimal — the bot only needs to post and read its own replies.

Wire it to the API

Add the channel ID to your environment and restart the API.

snippet
# apps/api/.env
TELEGRAM_BOT_TOKEN="123456:ABC-yourtoken"
TELEGRAM_BOT_USERNAME="SuppFrixBot"
TELEGRAM_CHANNEL_ID="-1001234567890"

# production webhook (https, public)
TELEGRAM_WEBHOOK_URL="https://support.frix.me/api/telegram/webhook"

In development the bot uses long-polling automatically. In production it registers the webhook above so Telegram delivers owner replies to the API.

Using the dashboard

  • Platforms — create one per app you embed in. Each gets its own API key and categories.
  • Categories — the options users pick from when writing in.
  • Tickets — a master-detail inbox. Pick a ticket, read the thread, and reply from the field at the bottom; the answer is pushed to the user.
  • Telegram toggle — turn channel/DM notifications on or off per platform.

API reference

The widget only touches the public endpoints; the rest power the dashboard and bot.

GET/api/platforms/categories?apiKey=…Public. Categories for a platform — used by the widget to populate the dropdown.
POST/api/tickets/createPublic. Create a ticket. Returns a publicToken the widget keeps to poll for replies.
GET/api/tickets/:publicTokenPublic. Ticket + full message thread, by token. Powers the widget conversation view.
POST/api/tickets/:publicToken/messagesPublic. Append a follow-up message from the user.
POST/api/auth/request-codeSend a 6-digit Telegram verification code to a telegramId.
POST/api/auth/verify-codeVerify the code, mark the user verified, return a short-lived token.
POST/api/auth/loginOwner login for the dashboard. Returns a JWT.
GET/api/owner/ticketsAuth. List tickets across the owner's platforms, filterable by status/platform.
POST/api/owner/tickets/:id/replyAuth. Post an answer. Delivered to the user via the widget and Telegram.
POST/api/telegram/webhookTelegram → API. Handles owner replies coming from the channel/DM.

Self-hosting

Support Frix is a small monorepo: a NestJS API, a Next.js dashboard, and the Vite widget.

snippet
# build everything
npm install
npm run build           # api + dashboard + widget

# database
npm run db:up           # postgres via docker
npm run db:deploy -w api

# run with pm2 (api :3000, dashboard :3001)
pm2 start ecosystem.config.js

nginx serves the built widget from /widget/, proxies /api/ to the API and everything else to the dashboard. The full nginx block ships in SETUP.md.