Webchatix documentation
Telegram-first live chat: the visitor writes into the website widget, your team answers from Telegram and the web dashboard. Sections on the left, details on the right.
In 5 minutes
- Open the bot →
/start— an account on the Free plan and the first site are created. - Copy the HTML snippet and paste it into your site before
</body>. /panel— a one-time link into the web dashboard (or the “Web dashboard” menu item).- Invite an operator:
/inviteor menu → “Team” → ➕. - Connect a Telegram group: add the bot to the group (or run
/addgroupthere) and assign it to a site under Routes. - Fallback for a whole site in one group:
/bindsite site_keyinside the group.
Plans and billing
Registration is open: /start in the bot creates an account on the Free plan. Paid plans are bought by card in the dashboard, section Billing — payment goes through Stripe.
| Plan | Websites | Clients / month | Operators | Per month |
|---|---|---|---|---|
free | 1 | 30 | 1 | $0 |
basic | 20 | 1000 | 5 | $10 ($8 paid yearly) |
unlimited | ∞ | ∞ | ∞ | $20 ($16 paid yearly) |
Prices are shown per month. Paying for a year is 20% cheaper and is charged as a single payment ($96 and $192 a year); yearly is preselected in the dashboard.
Premium (Basic and above): templates, active invite, visibility rules, notes, ratings.
A client is a unique visitor who started a chat in the current calendar month — the counter resets on the 1st.
How payment works
- Dashboard → Billing: current plan, usage, plan switch, list of invoices with PDF links.
- Card details never reach our servers — the payment page and the customer portal belong to Stripe.
- Changing the plan mid-period is prorated by Stripe; cancelling keeps the paid plan until the end of the paid period, then the account returns to Free.
- The email address in the same section receives the receipt, the failed-payment notice and a renewal reminder several days in advance.
Bot commands
| Command | Who | Meaning |
|---|---|---|
/start [CODE] | everyone | Onboarding / redeem an invite. |
/menu | member | The account main menu. |
/help | everyone | Command reference for your role. |
/panel | member | Magic link to the web dashboard. |
/newsite [name | origin] | admin+ | Create a site (+ CORS origins). |
/addgroup | admin+ in a group | Connect the group as a group operator (no chat_id needed). |
/bindsite KEY | admin+ in a group | Bind the group as the fallback chat. |
/invite | admin+ | Operator invite. |
/op_me CODE | anyone | Accept an invite. |
/tpl | member | List / add / del / send with reply + /tpl id. |
/note | member | Note on a dialog (Basic and above). |
/release | member | Release a dialog (+ ask for a rating). |
/lang | everyone | Bot interface language: en / pl / de / ru / uk / be / ar. |
/setrole id role | owner | owner|admin|operator. |
/crm on|off | owner | Turn the account CRM bridge on/off. |
/deleteaccount | owner | Delete the account. |
/id | in a chat | Show chat_id. |
Web dashboard
Tenant login: the link from /panel or a token on /account/login/.
| Section | What for |
|---|---|
| Overview | Counters and recent dialogs; the company name; the CRM bridge for the owner — switch and webhook link (empty falls back to the one in config). |
| Sites | Creation, snippet, full widget settings. |
| Operators | Members, invites, roles, groups. |
| Routes | Assignments: site → operator/group. |
| Dialogs | History, release, notes, rating. |
| Analytics | Visits, bots, forms and chat for all sites or one; pages; analytics-only code; the account API key. |
| Templates | Template CRUD + rating summary (Basic and above). |
Site settings
Available in the bot (“Settings”) and in the web dashboard under Sites → gear icon.
| Field | Meaning |
|---|---|
| Name / title / subtitle | The chat header. |
| Greeting / logo | The first message and the brand. |
| Online from–to, TZ, pause | The Online/Offline status. |
| Escalation (min) | Alert when there is no answer. |
| Colour / position / offsets | The widget button. |
| Language / sound | en|pl|de|ru|uk|be|ar and an mp3 URL. |
| Allowed origins | CORS: comma-separated domains or *. |
| Invite * | Bubble invitation (Basic and above). |
| Visibility JSON | Where to show the button/invite (Basic and above). |
| GDPR | Consent checkbox before the first send. |
| AI responder | Answers the visitor when nobody replied for N minutes (3 by default). |
AI responder (Gemini)
The visitor wrote, the operator is silent — after the configured number of minutes Google Gemini answers in the chat. The visitor sees the reply in the widget, and operators get an “AI replied” card in Telegram with the same text, so nobody repeats it.
- Enabled per site: Sites → gear → AI responder. The same block holds the delay and the “what the AI should know” field — opening hours, delivery, what must not be promised.
- A human reply resets the timer; a conversation an operator has claimed is left alone.
- An AI answer does not cancel escalation: the admin is still alerted that no human replied.
- The model keeps answers short, never invents prices or dates, and admits to being an automated assistant when asked directly.
- The API key is set once per installation:
gemini_api_keyinconfig/config.php(take it in Google AI Studio, it has a free tier). Without the key the checkbox does nothing.
Visibility example
{"include":["/pricing","/contacts"],"exclude":["/admin"],"desktop":true,"mobile":true} Website widget
<script async src="https://webchatix.online/widget/webchatix.js"
data-site-key="SITE_KEY"></script>
Control from the page:
webchatix("show"); // show the button
webchatix("hide"); // hide it
webchatix("open"); // open the chat
webchatix("close"); // close the panel
webchatix("form", "report"); // count a form submission (analytics)
The widget language follows data-lang, then the page <html lang>, then the browser; en / pl / de / ru / uk / be / ar are supported, with English as the fallback. After changing the settings, reload the client page (the widget version sits in the ?v= query).
If your site sets a Content-Security-Policy, allow the widget domain in three directives: script-src, style-src and connect-src. The widget loads its styling from a separate webchatix.css file, so you do not need to keep 'unsafe-inline' in style-src for it.
Visit analytics and API
The widget counts visits to the site it is installed on. Analytics is kept with no time limit as daily counters (days in UTC); a report can cover any period. The numbers are available through an API with the secret account key — one key opens all sites of the account.
| Field | What is counted |
|---|---|
unique_visitors | Unique visitors — by the widget cookie (without a cookie — by IP + browser per day). Across a period there are no repeats: one person over a week counts once. |
visits | Entries — the first page after 30 minutes of inactivity, plus a visitor's first page of the day. |
page_views | Page views by people, including SPA navigation (history.pushState). |
bot_hits | Pages crawled by bots: search engines (Googlebot, Bingbot, YandexBot…), AI crawlers (GPTBot, ClaudeBot, PerplexityBot…), social networks, SEO tools, headless browsers. They are not included in views or unique visitors. |
form_submits | Form submissions on the site pages (the submit event, including forms the site sends via fetch). Field values are not sent — only the form name: #id, name or the action path. Submissions by bots are not counted. |
chat_messages | Visitor messages in the Webchatix chat (text and photos). |
chat_visitors | How many visitors wrote in the chat — by conversation, without repeats across the period. |
ads_google | Clicks from Google Ads: the landing page URL has gclid, gbraid, wbraid or gad_source, or utm_source=google with a paid utm_medium (cpc, ppc, paid…). Counted once per entry: reloading the page does not double the click. |
ads_yandex | Clicks from Yandex Direct: the URL has yclid, or utm_source=yandex with a paid utm_medium. Also once per entry. |
The regular chat snippet already collects analytics — nothing to add.
Analytics-only install (no UI)
The data-mode="stats" attribute installs the widget without the button, chat window or styles: the script only sends one lightweight hit per page. The <noscript> pixel is optional — it catches bots that do not run JavaScript but do load images.
<script async src="https://webchatix.online/widget/webchatix.js"
data-site-key="SITE_KEY" data-mode="stats"></script>
<noscript><img src="https://webchatix.online/wx-api.php?action=track&site_key=SITE_KEY"
width="1" height="1" alt="" style="position:absolute"></noscript>
SITE_KEY is the public site key — the same one as in the chat snippet (bot: “📋 Install code”, dashboard: “Sites”). It is not the wxs_… API key: that one is secret and never goes into page code.
If the site has Allowed origins set, hits from pages on other domains are not counted.
Forms without a <form> tag
The widget catches a regular <form> by itself — via the submit event, including forms the site sends via fetch. But a form is often just fields plus a <button type="button">, with the site's JavaScript sending the data; there is no submit event then, and the submission is not counted. There are two ways to handle such forms.
1. The data-wx-form attribute on the submit button: a click on it counts as a submission of the form with that name. A disabled button is not counted, and a double click gives one submission.
<button type="button" class="get_report" data-wx-form="report">…</button>
2. A call from your site's code — when a submission should count, for example, only after the fields pass validation. It works with the regular widget and with data-mode="stats". If your code can run before the widget loads, add the stub above it — the calls are queued and not lost.
// after the fields pass validation
webchatix("form", "report");
// stub — above the calls, if your code may run before the widget
window.webchatix = window.webchatix || function () {
(webchatix.q = webchatix.q || []).push(arguments);
};
The data-wx-form attribute on a <form> or on its submit button sets the form name in the report instead of #id / name / action.
API key
The wxs_… key is one per account. Where to get and replace it: in the Telegram bot — the 🔑 API key button in the main menu (with a summary of all sites); in the dashboard — Sites → gear icon. “Replace key” issues a new one and the old key stops working immediately. The account owner and admins can see and replace it.
The key is secret: the public site_key cannot be used for reading, and the key itself must not go into page code — request analytics from your server. One site's analytics is also in the bot: 🌐 Sites → site → 📊 Analytics.
Pass the key in one of three ways: the X-Stats-Key: wxs_… header, the Authorization: Bearer wxs_… header, or the ?key=wxs_… parameter.
Two keys
| Key | Where it is used | Public? |
|---|---|---|
SITE_KEY — site key | chat snippet, data-mode="stats", the wx-api.php?action=track pixel — everything that sits in page code and only writes hits | yes |
wxs_… — account API key | stats-api.php: reading analytics (action=stats) and server-side hits (action=hit) — from your server only. One for all sites of the account. | no, secret |
An API key in the pixel or page code is visible to any visitor via “View source” — they could read and inflate your analytics. If the key has leaked, replace it.
GET /stats-api.php?action=stats
Parameters: site_key — only this site of the account (without it — all sites); the period — days (the last N days up to today, 1–366, default 7) or from and to (YYYY-MM-DD dates, inclusive, at most 366 days; if only one date is given, the other is counted days away); limit — how many pages in each site's top list (1–1000, default 50).
curl -H "X-Stats-Key: wxs_…" "https://webchatix.online/stats-api.php?action=stats&days=7&limit=20"
curl -H "X-Stats-Key: wxs_…" "https://webchatix.online/stats-api.php?action=stats&site_key=SITE_KEY"
curl -H "X-Stats-Key: wxs_…" "https://webchatix.online/stats-api.php?action=stats&from=2026-01-01&to=2026-03-31"
{
"ok": true,
"account": {"id": 42, "name": "My company"},
"timezone": "UTC",
"from": "2026-10-12",
"to": "2026-10-18",
"totals": {"unique_visitors": 412, "visits": 530, "page_views": 1874, "bot_hits": 961,
"form_submits": 23, "chat_messages": 57, "chat_visitors": 14,
"ads_google": 48, "ads_yandex": 31},
"days": [
{"date": "2026-10-18", "unique_visitors": 61, "visits": 74, "page_views": 255, "bot_hits": 140,
"form_submits": 4, "chat_messages": 9, "chat_visitors": 3,
"ads_google": 6, "ads_yandex": 4}
],
"sites": [
{
"site_key": "SITE_KEY",
"name": "My site",
"totals": {"unique_visitors": 300, "visits": 391, "page_views": 1402, "bot_hits": 700,
"form_submits": 15, "chat_messages": 40, "chat_visitors": 9,
"ads_google": 35, "ads_yandex": 20},
"days": [ … ],
"pages": [{"path": "/pricing/", "page_views": 310, "bot_hits": 88, "form_submits": 6}],
"bots": [{"bot": "Googlebot", "hits": 402}, {"bot": "GPTBot", "hits": 97}],
"forms": [{"form": "#contact", "submits": 15}]
}
]
}
totals and days are the sum over the sites in the response; visitors are counted per site. sites holds each site's report: days has one entry per date (days without visits come with zeros), pages is the top pages (path without the query string) by views plus crawls, bots is crawls per bot name, forms is submissions per form name.
POST /stats-api.php?action=hit — server-side hits
Most search bots do not run JavaScript, so the widget does not see them. To count them as well, your site backend can report the requests it receives: fields site_key (which site of the account; required), url (or a path), ua — the User-Agent, optional ip, referrer, visitor_id. Batch: {"site_key": "…", "hits": [ … ]}, up to 500 hits per request; a hit's own site_key overrides the shared one. Bot or human is decided by ua.
curl -X POST "https://webchatix.online/stats-api.php?action=hit" \
-H "X-Stats-Key: wxs_…" -H "Content-Type: application/json" \
-d '{"site_key": "SITE_KEY", "url": "https://example.com/pricing/", "ua": "Mozilla/5.0 (compatible; bingbot/2.0)", "ip": "203.0.113.7"}'
# → {"ok": true, "views": 0, "bots": 1, "skipped": 0}
Errors
| HTTP | error | Reason |
|---|---|---|
| 401 | key_required | No key was passed. |
| 403 | invalid_key | The key is wrong or has been replaced. |
| 403 | plan_required | The API key works on the Basic and Unlimited plans. |
| 403 | disabled | The account is suspended. |
| 400 | too_many_hits | More than 500 hits in one request. |
| 400 | unknown_site | site_key is missing or belongs to another account. |
| 400 | bad_period | Bad period: a date is not YYYY-MM-DD, from is after to, or the period is longer than 366 days. |
Only counters and anonymised visitor hashes are stored — no IPs, addresses or User-Agents in plain form.
Operators and roles
- owner — full control, plan requests, account deletion, CRM, ownership transfer.
- admin — sites, invites, routes, settings.
- operator — answers in dialogs, notes/templates depending on the plan.
One Telegram user = one owned account. In other people's accounts you can be an operator by invitation.
Groups
Besides people, a Telegram group can be an operator: requests arrive as a card in the chat and anyone in the group answers by replying to it. To connect one, add the bot to the group (menu → 💬 Groups → “Connect a group”) or run /addgroup inside it — no chat_id to type, the bot reads it itself. Groups do not count against the plan's operator limit.
Dialogs
- The customer message goes to Telegram (group/operator according to the route).
- Claim — takes the dialog; a system message lands in the history.
- Transfer — only to an active member of the same account.
- Release — you can ask the visitor for a rating.
- Notes — visible to the team, not to the customer (Basic and above).
- Templates — reply to the dialog plus
/tpl id, or the template button → Reply.
Security and GDPR
- Accounts are isolated by
account_id. - The magic link is single use; link issuing is rate limited.
- Suspend switches off the widget, the bot and the tenant dashboard.
- CORS: set allowed_origins (do not leave
*in production without a reason). - The GDPR checkbox comes before the customer's first message.
- Upload quota depends on the plan (
max_upload_mb). - No more than 10 new sites per hour per account.