Qiksy AssistDocs
Qiksy Assist · Documentation

Everything the widget can do

Install it, configure it, drive it from your own code, or talk to the API directly. Written against the shipping source — if something here disagrees with the product, the documentation is the bug.

Quick start

One script tag, one Telegram connect. Two minutes, no build step, no server of your own.

  1. 1Create an assistant at qiksy-assist.qiksy.workers.dev/start. You get a tenant key (qa_…), a ready snippet and a private auto-login link to your panel — save that link before you leave the page. It is shown once and is never emailed to you.
  2. 2Paste the snippet before </body> on your site. The bubble appears immediately.
  3. 3Open your settings panel and press Connect Telegram. It deep-links the bot assigned to you and binds the chat in one tap — messages from the site then arrive there. (Always take that link from the panel: assistants are spread over several bots, so the right one is the one your panel names.)
  4. 4Reply with a normal swipe-reply. Your answer appears in the visitor’s widget within seconds.
<script src="https://qiksy-assist.qiksy.workers.dev/widget.js?key=qa_yourkey" defer></script>
The snippet needs no other parameters — the worker serves the widget with your endpoint and key already wired in. Everything else (brand, colours, greeting, channels) is configured in the panel and applied live, without touching the page again.

Visitors stay anonymous: no account, no login, no tracking cookie. A session id lives in their localStorage for 30 days so the conversation survives a reload.

Installing on your site

The widget is one self-contained file with no dependencies. Anywhere you can add a script tag, it works.

Plain HTML

Paste the snippet before the closing </body> tag. defer keeps it off the critical path — the widget never blocks your page render.

WordPress

Appearance → Theme File Editor → footer.php, before </body>. Or use any “insert header and footer” plugin and put the snippet in the footer field — no theme edit, survives theme updates.

Tilda

Site Settings → More → HTML code for the BODY section. Paste, save, republish the site (Tilda only ships the change on republish).

Shopify

Online Store → Themes → Edit code → layout/theme.liquid, before </body>.

Webflow

Project Settings → Custom Code → Footer Code. Publish to apply — custom code does not run in the Designer preview.

Next.js / React

Render it as a real script tag, not an import. In the App Router, put it in app/layout.tsx:

import Script from 'next/script'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Script
          src="https://qiksy-assist.qiksy.workers.dev/widget.js?key=qa_yourkey"
          strategy="lazyOnload"
        />
      </body>
    </html>
  )
}
Single-page apps: the widget mounts once and survives client-side navigation on its own. Do not re-inject the script on route change — it guards against double-mounting via window.__qiksyAssist, so a second injection is a no-op, but a removed-and-re-added script loses the open conversation panel.

Self-hosting the file

You may host widget.js yourself instead of loading it from our worker. Then the endpoint is no longer implied and you pass it explicitly:

<script src="/js/widget.js" defer
        data-endpoint="https://qiksy-assist.qiksy.workers.dev"
        data-key="qa_yourkey"></script>

You then own updates: fixes and new features ship to the worker-served copy automatically, to your copy only when you replace the file.

Connecting Telegram

Telegram is your side of the conversation. Visitors never see it, never need it, and never learn your number.

Connecting

Open the deep-link in your settings panel — it is t.me/<your bot>?start=<your key> — and press Start. The chat is bound to your assistant and site messages begin arriving there.

A binding can be claimed once. Your tenant key is public — it ships in the snippet on your site — so a second chat cannot silently take over your messages. Changing Telegram accounts therefore needs the old binding released first: the panel’s Disconnect button does that for an own-bot connection, but a one-click connection on a shared bot has no self-serve release yet — ask us in the widget and we will free it.

Replying

  • Swipe-reply to a message — the standard Telegram reply gesture. That is the whole workflow.
  • /r CODE text — reply by the four-character conversation code shown in the ping (💬 #A7K2), useful when the original message has scrolled away.
  • /admin — the bot re-sends your auto-login panel link. It is your keychain; do not forward that message.

The bot reacts 👍 when your reply has been routed to the visitor. No reaction means it could not tell which conversation you meant — reply directly to the message, or use the code form.

Files

Both directions. Visitors may send PNG, JPEG, WebP or PDF up to 10 MB; you reply with a photo or document as a reply. Files are relayed through Telegram, which is also where they are stored — nothing is kept on our side beyond the file reference. Because Telegram is the store, uploads only work once your Telegram is connected — before that a visitor trying to attach a file is told so. Text messages, meanwhile, are stored and replayed to you on first connect.

Which bot serves you

Telegram caps one bot at roughly 30 messages per second across all of its chats, so assistants are distributed over a pool of bots. Yours is assigned when you sign up, and the deep-link in your panel always points at the right one — you never pick.

On the Pro plan you may connect your own bot instead: paste its @BotFather token in the panel and the assistant runs under your brand, with a delivery ceiling nobody else shares. An already-connected own bot keeps working on any plan.

The settings panel

Everything visible in the widget can be changed without touching your site again.

Your panel lives at /admin?key=… on the worker. The auto-login link is shown once, on the page where you create the assistant — bookmark it there and then. The token rides in the URL fragment, so it never reaches the server as a query parameter. Once Telegram is connected you can always have the link re-sent by messaging /admin to the bot — but that needs a binding to already exist, which is why the link matters before you connect anything.

  • Telegram — connect, test, disconnect; a no-terminal wizard including your own bot token.
  • Appearance — brand name, accent colour, theme, position, logo (upload with circle-crop, or a URL).
  • Texts — greeting, tagline, teaser bubble, the menu of request types.
  • Channels — which of chat / request / subscribe / WhatsApp / video / voice are offered.
  • Inbox — every lead and conversation stored on our side, readable even with Telegram completely down.
  • Live preview — a real widget in a drawer that re-renders on every field edit.
Panel settings are served to the widget as remote config with a 60-second cache. Anything you set explicitly in the script tag wins over the panel — see the precedence rule in Configuration.

Owner mode

Open your own site with #qas-owner appended to the URL. That browser is remembered as yours, and service notices (Telegram not connected, trial ending) are shown only to you. Visitors never see them.

Configuration reference

Every option can be set as a data-attribute on the script tag, or on a window.QiksyAssist object before the script loads.

Precedence

Explicit data-* or window.QiksyAssist → remote config from the panel → built-in default. An option you hard-code on the page can never be overridden from the panel; that is deliberate, so a deploy cannot be undone by a settings change.

Connection

AttributeDefaultWhat it does
data-keyTenant key (qa_…). Pre-wired when the widget is loaded from the worker with ?key=.
data-endpointWorker base URL, or the full /lead URL. Only needed when self-hosting the file.
data-emailFallback mailbox. With no endpoint, the request flow degrades to the visitor’s mail app instead of breaking.

Appearance

AttributeDefaultWhat it does
data-brandAssistantName shown in the widget header.
data-accent#EAB308One hex colour. The entire palette — gradients, glows, contrast ink — is derived from it.
data-themeautoauto | dark | light. Auto follows the visitor’s system setting.
data-positionrightright | left corner.
data-offsetx18Pixels from the chosen side.
data-offsety18Pixels from the bottom.
data-zindex2147482000Stacking order. Raise it only if something on your site still covers the bubble.
data-logoEmpty = the default mark · none = no mark · an image URL or data URI. Pro — on a lower plan the custom image is ignored and the default mark shows.
data-brandingonoff hides the “⚡ Qiksy Assist” note. Requires a paid plan.
data-mobileonoff hides the widget below 640px.

Language and copy

AttributeDefaultWhat it does
data-langautoauto | en | ru | uk. Auto picks by visitor region.
data-greetingFirst line the assistant says.
data-taglineReply-time line under the brand. Keep it honest — it sets the expectation you are judged against.
data-teaserThe small bubble that appears next to the launcher before anyone clicks.
data-intentsComma-separated menu of request types, e.g. Pricing, Book a call, Support.
data-budgetsComma-separated budget buckets. Any currency, any market.

Channels

AttributeDefaultWhat it does
data-modeschat,order,subscribeWhich entries the menu offers: chat, order, subscribe, email. Any combination, any single one. WhatsApp, Telegram and video are not set here — each switches itself on when its own option is set.
data-whatsappDigits-only number (8–15). Adds a WhatsApp hand-off that carries the conversation context.
data-telegram@username or t.me link. Adds a direct-message button. Paid channel — hidden on Free, exactly like WhatsApp.
data-videooffon offers a browser video room; the link is delivered as a lead.
data-miconoff hides voice dictation. Rendered only where the browser supports it.
data-budgetonoff removes the budget step from the request flow.
Every capability is independently switchable, from the attributes or the panel. Two things about the extremes: the premium channels (WhatsApp, Telegram, video) are additive — they appear alongside the menu rather than replacing it — and if data-modes ends up empty the widget falls back to the request flow, so a visitor always has a way to reach you. Voice is an input method inside chat, not a menu entry of its own.

Known visitors

AttributeDefaultWhat it does
data-nameName of a logged-in visitor.
data-contactTheir email or phone. Present = the contact step is skipped.
data-user-metaFree text you want to see next to them, e.g. Plan Pro · client since 2024 · ID 8842.

For values known only at runtime use identify() instead — see the JavaScript API.

Object form

Set window.QiksyAssist before the script loads. The keys are the attribute names minus the data- prefix — including the hyphen, so it is 'user-meta', not userMeta. A camelCase key is silently ignored. Attributes on the script tag still win over this object.

<script>
  window.QiksyAssist = {
    key: 'qa_yourkey',
    brand: 'Northwind',
    accent: '#2563EB',
    intents: ['Pricing', 'Book a demo', 'Support'],
    'user-meta': 'Plan Pro · ID 8842'
  }
</script>
<script src="https://qiksy-assist.qiksy.workers.dev/widget.js" defer></script>

JavaScript API

The widget exposes exactly one global. Everything else stays inside its shadow root.

window.__qiksyAssist = {
  version: '0.2.0',
  open(),                                  // open the panel
  close(),                                 // close it
  toggle(),                                // flip it
  ask({ intent, context }),                // one-step lead from any CTA
  identify({ name, contact, meta })        // seed a known visitor
}
The object appears asynchronously — the script is deferred. Guard your calls (window.__qiksyAssist?.open()) or run them from a user gesture, by which time it always exists.

Turning any button into a lead

Add data-qiksy-ask to any element on your page. Clicking it opens the widget into a one-step contact flow and sends a lead carrying that intent, the page and any context you attach — no form, no separate landing page.

<button data-qiksy-ask="Request a quote"
        data-qiksy-context="Plan: Business · Seats: 25">
  Get a quote
</button>

For context computed at runtime, call it in JavaScript:

document.querySelector('#quote').addEventListener('click', () => {
  window.__qiksyAssist.ask({
    intent: 'Request a quote',
    context: 'Cart: ' + cart.total + ' · Items: ' + cart.count
  })
})

Identifying a logged-in visitor

Call identify() once you know who the visitor is. You then see a real person in Telegram instead of an anonymous code. A known contact also skips the contact question in live chat — the request and subscribe flows still ask, because there the contact is the point of the form.

window.__qiksyAssist.identify({
  name: 'Anna Kovalenko',
  contact: '[email protected]',
  meta: 'Plan Pro · client since 2024 · ID 8842'
})

One more global worth knowing: ask({context}) writes window.__qiksyLeadContext on your page and never clears it, so every later lead from that page carries the same context until something overwrites it. Set it fresh per call if that matters. meta is free text up to 300 characters and appears as 👤 … on the first ping of every conversation. Send what makes the reply better — plan, order number, lifetime value — and nothing you would not want in a Telegram message.

Delivery channels

Where messages and leads go, and what happens when a channel is down.

  • Telegram — for a hosted assistant, the delivery channel. Two-way: pings out, swipe-replies back.
  • Email and webhook mirroringself-hosted deployments only. They are configured deployment-wide (RESEND_API_KEY + LEAD_EMAIL_TO/LEAD_EMAIL_FROM, FORWARD_WEBHOOK) and are deliberately switched off for hosted tenants, so one customer’s leads can never reach another’s mailbox.
  • The panel inbox — every lead and conversation is stored and readable in your panel regardless of channel, which is what makes a Telegram outage survivable.

Nothing is lost when a channel fails

Four independent layers, because a visitor must never see an error caused by our outage:

  1. 1Every lead is stored before any delivery is attempted, and the visitor gets a success response even if every channel fails.
  2. 2Telegram calls retry three times with backoff, honouring rate-limit hints.
  3. 3Undelivered pings and leads are re-sent automatically on the next successful delivery, and on first Telegram connect — up to 72 hours back.
  4. 4The panel inbox reads straight from storage, so you can work through everything with Telegram fully down.
Attachments are the exception to all four layers. A file is relayed to Telegram in a single attempt — no retry, no backlog, no replay — because Telegram is also where it is stored; if that call fails the visitor is told the upload failed and can retry. Text messages and leads are covered as described above.
This is also why the first thing you see after connecting Telegram may be a small backlog: messages people sent before you connected are delivered then, not discarded. The replay is capped — up to 10 recent visitor messages and 10 leads from the last 72 hours. Anything beyond that is still stored and readable in the panel inbox; it just is not replayed into the chat.

HTTP API

The contract between the widget and the worker. You need this only for a custom client or a server-side integration.

Base URL: https://qiksy-assist.qiksy.workers.dev. Every route takes ?key=<tenant key>. CORS is open; the endpoints below are the public surface.

MethodRoutePurpose
POST/leadSubmit a lead. Fans out to every configured channel.
POST/chatSend a chat message (JSON, or multipart with a file).
GET/chatPoll for owner replies: ?session=&after=.
GET/configPublic widget config for this tenant. Cached 60s.
GET/fileSession-scoped attachment proxy: ?session=&m=.
GET/healthReturns ok. For uptime checks.

Lead payload (v1)

{
  "v": 1,
  "lang": "en",
  "page": "https://yoursite.com/pricing",
  "intent": "Request a quote",
  "brief": "Need 25 seats, starting in March",
  "budget": "$1k–5k",
  "contact": "[email protected]",
  "name": "Anna Kovalenko",
  "context": { "summary": "Plan: Business", "link": "https://yoursite.com/pricing" },
  "dwellMs": 9400,
  "hp": ""
}

context must be an object ({summary, link}) — a plain string is accepted by the request and then silently dropped. hp is a honeypot and must stay empty. dwellMs is how long the visitor spent before submitting; anything under 2.5 seconds is treated as automated. Both cases return a normal 200 and deliver nothing — bots are not told they were caught.

Chat protocol (v1.1)

POST /chat
{
  "v": 1,
  "type": "msg",
  "session": "qs_a1b2c3d4",
  "text": "Do you ship to Poland?",
  "lang": "en",
  "page": "https://yoursite.com/",
  "dwellMs": 5200
}
→ { "ok": true, "id": 12, "code": "A7K2" }

GET /chat?session=qs_a1b2c3d4&after=12
→ { "ok": true, "messages": [ { "id": 13, "text": "Yes — 2–4 days.", "ts": 1785… } ] }

The session id is generated by the client and must match qs_[a-z0-9]{8,32}. GET /chat returns owner messages only, newer than after. Polling is not just etiquette — it is enforced (see the limits below), so match the widget’s cadence: 3.5s with the panel open, backing off to 60s idle, paused entirely on a hidden tab.

Rate limits are enforced per session and per IP, and the tighter one wins. Chat: 15 messages/minute per session, but also 12/minute and 90/hour per IP. Leads: 6/minute and 20/hour per IP. New sessions: 6/hour. File uploads: 10/hour. Polling GET /chat is metered too — 120/minute and 2000/hour per IP. Exceeding any of them returns 429 with {ok:false, retry:true}. Behind one office NAT these are shared by everyone on that address.

Versioning

The v field is the contract version. Breaking changes bump it and the previous version keeps being accepted — an embed that works today is not broken by a later release.

Privacy and security

What is stored, where it lives, and what the widget will never do to your page.

What is stored

  • Conversations and leads for your assistant, in an isolated database of your own. Every tenant is a separate storage object — not rows in a shared table.
  • On the visitor’s device: a session id and their own message history in localStorage, expiring after 30 days.
  • Attachments are relayed to Telegram, which is also the storage. We keep the reference, not the file.

No visitor accounts, no cross-site tracking, no advertising identifiers, no third-party analytics inside the widget.

What it will not do to your page

  • It renders inside a shadow root, so your CSS cannot break it and it cannot leak styles into your page. Every class is prefixed qas-.
  • It defines exactly one global, window.__qiksyAssist.
  • It never opens itself. No auto-open, no exit-intent pop-up.
  • It asks for contact details only after the first message, and the visitor can skip it.

Content Security Policy

If your site sets a CSP, allow the worker as a script and connect source. Two more directives matter: the launcher mark and the brand bolt are inlined as data: images, so img-src must allow data:; and the widget injects one stylesheet, copying your page’s nonce onto it — so a nonce-based policy needs no unsafe-inline, but the style element does have to be permitted:

script-src 'self' https://qiksy-assist.qiksy.workers.dev;
connect-src 'self' https://qiksy-assist.qiksy.workers.dev;
img-src 'self' data:;
style-src 'self' 'nonce-<your-nonce>';

Spam protection

A honeypot field, a dwell-time gate and per-IP and per-session rate limits, all before anything reaches your Telegram. A submission that fails the dwell-time gate is kept and shown in the panel inbox as suspect rather than pinged to your phone. A filled honeypot — a field no human can see — is dropped entirely and stored nowhere: only automated traffic ever fills it. Both get a normal success response, so a bot is never told it was caught.

Plans and limits

Chat is free forever. Paid plans remove the badge and add premium channels.

FreeStartPro
Two-way Telegram chatUnlimitedUnlimitedUnlimited
Leads, files, anti-spamYesYesYes
Panel inbox (readable with Telegram down)YesYesYes
“⚡ Qiksy Assist” badgeShownRemovedRemoved
WhatsApp button, Telegram button, video call, voice inputYesYes
Custom logo in the widgetYes
Your own Telegram botYes

New assistants get a 14-day Start trial — the badge removal and the premium channels, not the Pro extras. When it ends, chat keeps working: it never locks. Only the premium extras switch off, and a 7-day grace period runs before even that.

Connecting your own Telegram bot is a Pro feature — that is how you run the assistant under your own brand, with a delivery ceiling nobody else shares. If you already have one connected it keeps working whatever your plan says: a live site’s delivery channel is not something to switch off over billing.
Payment is a one-time charge for a one-year term. No stored card, no automatic renewal, nothing to cancel.

Conversation limits

There are none. No cap on conversations, no per-seat pricing, no “active contacts” meter. The rate limits described in the HTTP API are anti-abuse floors, not a plan quota — normal traffic never reaches them.

Troubleshooting

The bubble does not appear

  • Check the browser console for [qiksy-assist] warnings — the widget says out loud when it has nowhere to send messages.
  • Confirm the script tag is in the page source (view-source, not the inspector) — some builders strip unknown tags on publish.
  • On Tilda and Webflow the change only ships on republish.
  • If your site sets a CSP, see the policy snippet under Privacy and security.
  • Below 640px the widget is hidden when data-mobile="off".

Messages do not reach Telegram

  • Open the panel and press Test in the Telegram card — it sends a message right then and shows whether it went through. It reports success or failure, not the reason, so if it fails work through the two causes below.
  • If you never pressed Start in the bot, the binding does not exist yet. Nothing is lost: connect now and the backlog is delivered.
  • If you changed Telegram accounts, disconnect in the panel first — a binding cannot be silently reassigned.

My reply did not reach the visitor

Check for the 👍 reaction on your message. No reaction means the reply was not attributed to a conversation — use a real swipe-reply, or /r CODE text with the code from the ping.

The widget looks wrong on my site

It cannot inherit your CSS — it lives in a shadow root. What it does inherit is stacking: if something covers it, raise data-zindex. If it overlaps a fixed footer or a cookie bar, move it with data-offsety.

Still stuck

Use the widget on this page — it goes to the same place your customers’ messages go, and a person answers it.