#Modules for Websites
Status: Available: Bitrix
sapport.connector1.2.1 and WordPresssapport-connector-wp1.1.1. Beta: FastAPIsapport-connector1.0.0 and Nuxt@sapport/nuxt-connector1.0.0. The packages are ready, but the dashboard marks them "coming soon" (not published).
Purpose: connect a site built on a CMS or framework to Walker Formulation Academy with a single module: visit counter, server beacon (visible to bots and AI crawlers), chat, behavior tracking, and publishing of articles from Walker Formulation Academy.
#Contents
- When to use it
- Common to all modules
- Bitrix
- WordPress
- FastAPI
- Nuxt
- Connector protocol for custom modules
- Open questions
#When to use it
| Situation | Solution |
|---|---|
| The site runs on Bitrix (not cloud Bitrix24) | Bitrix module |
| The site runs on WordPress | WordPress module |
| The site runs on FastAPI + PostgreSQL | FastAPI module (+ Nuxt for the frontend), Beta |
| Another platform, only a counter is needed | JS tracker by token |
| Another platform, chat and behavior tracking are needed | Loader |
| A custom module for a non-standard platform | the Connector protocol section |
#Common to all modules
#Access
| Who binds | a dashboard user with access to "Promotion → System → Sites (module)"; the tracker key is issued by the seo.manage permission |
| Paid | no, there is no separate fee for the module |
| Personal data | IP and User-Agent hashes, country (in full mode); see Overview |
#What the module does
| Capability | Description |
|---|---|
| Server beacon | after the page response, the module sends a signed hit to the cloud (path, referrer, User-Agent, IP). It sees bots that do not execute JS |
| Loader | the loader.js tag (counter, behavior tracking, chat) is output in <head> |
| Heartbeat | once an hour the module reports its state and receives its configuration |
| Publisher | takes article publishing jobs from the cloud and applies them (as drafts by default) |
| Kill switch | disabling the site in the dashboard stops all parts |
#How to bind a site: the binding code
- In the dashboard: "Promotion → System → Sites (module)" → add the domain → get a binding code.
- The code carries the cell prefix:
RU-…,EN-…, orID-…. It lives for 15 minutes and is single-use. A code from another cell gives the error "code not found". - Enter the code in the module. The module exchanges it for
site_id, a public key, a secret, and a beacon key (the secret is returned once). - If the code has expired or been used, click "New code". "Disconnect" unbinds the site on the cloud side.
Site states in the dashboard: pending (code issued, not yet bound), bound, disabled.
Binding accepts only the registered domain. A domain alias or another host gives the error domain_mismatch (on binding) or 400 (on the server beacon); www. is supported if the module accounts for it.
#Download
Archives and SHA-256 checksums are in the dashboard: "Settings → Integrations → Modules for websites". Verify the checksum before installing.
| Module | Version | File | Status |
|---|---|---|---|
| Bitrix | 1.2.1 | /connector/sapport.connector-1.2.1.zip | Available |
| WordPress | 1.1.1 | /connector/sapport-connector-wp-1.1.1.zip | Available |
| FastAPI | 1.0.0 | sapport_connector-1.0.0-py3-none-any.whl + documentation archive | Beta, not published |
| Nuxt | 1.0.0 | sapport-nuxt-connector-1.0.0.tgz | Beta, not published |
Files are served from your cell's domain (RU https://formula-cream.pro, EN https://wfacademy.org, ID https://wfacademy.id).
#Server beacon modes
server_beacon | What is sent | Default for |
|---|---|---|
full | path, referrer, User-Agent, IP, country | RU |
anonymous | the same, but the cloud does not store the IP/UA hashes, the UA sample, or the country | EN, ID |
off | nothing | n/a |
#Bitrix: sapport.connector 1.2.1
Status: Available.
#Requirements
| Edition | any, starting from "Start" (not cloud Bitrix24) |
| PHP | 7.4–8.3 with the curl extension |
| Network | outbound HTTPS to your cell's domain |
| For the publisher | the iblock module, the sodium extension, the CREATE TABLE privilege |
#Installation
- Unpack the archive into
/local/modules/. - In the admin area, open "Marketplace → Installed solutions" (
/bitrix/admin/partner_modules.php) and click "Install" next to "Sapport: connector". - Get a binding code in the Walker Formulation Academy dashboard (see above).
- "Settings → Product settings → Module settings → Sapport: connector" → enter the code → "Bind".
- Click "Check connection".
Recommendations:
- Run Bitrix agents from cron (otherwise the heartbeat and the publisher lag).
- If the site runs behind a proxy and passes
X-Forwarded-For, tick "Site runs behind a proxy"; otherwise the visitor's IP will be replaced with the proxy's address.
#What the module does
| Function | Description |
|---|---|
| Beacon | in the init.php cycle it sends a signed hit; it skips CLI, /bitrix, upload, local/templates, static files, ajax, non-GET requests, and requests without text/html in Accept |
| Loader | outputs the loader.js tag |
| Page context | \Sapport\Connector\Loader::setPage('iblock_element', $id); tells the chat which page is being discussed |
| Publisher | creates items in infoblocks from cloud jobs. Draft mode by default; service properties SAPPORT_SOURCE_ID, SAPPORT_SOURCE_HASH, SAPPORT_VERSION, SAPPORT_JOB; a ledger in the b_sapport_connector_ledger table |
| Sections | allowed infoblock sections (up to 500) |
| IndexNow | notifies search engines of new pages (up to 100 per run) |
| Sitemap | /sapport-sitemap.xml (up to 50,000 URLs); returns 404 if the SEO module updates the sitemap automatically |
#Settings
| Parameter | Purpose |
|---|---|
| Binding code | single-use, from the dashboard |
| Site runs behind a proxy | trust X-Forwarded-For |
| Publisher mode | draft (default) or publish, if the ceiling allows |
| Allowed sections | where the publisher may create items |
| Indexing | IndexNow, sitemap |
Which parts are on (tracker, behaviour, chat, the consent mode, the beacon mode) is set in the dashboard.
#Verification
- The "Check connection" button → success.
- In the dashboard, the site state is
bound. - After a visit from an ordinary browser, the counter in Overview grows; a bot hit can be checked with
curl -A "Googlebot" https://your-site/. - The page outputs the
loader.js?k=…tag in<head>.
#Update and removal
- Update: replace the module folder contents with the new version; rebinding is not needed. If the cloud answers
update_required(unknown_kidorkid_wrong_cell), update the module: it does not know the job signing key. - Removal: Marketplace → "Delete". Unbind the site beforehand ("Disconnect" in the dashboard), otherwise the binding stays on the cloud side.
#Common problems
| Symptom | Cause and solution |
|---|---|
| No hits from bots | Bitrix composite cache serves pages without calling PHP; the beacon does not see them |
| State "key error" | the cloud rejected the signature 3 times in a row. The secret is damaged or the site was reissued: unbind and bind again |
code_not_found | the code has expired, been used, or is from another cell (wrong prefix) |
| All visitors have the same IP | enable "Site runs behind a proxy" |
#Security
The binding secret is stored in the b_option table in clear text, so database backups contain it. The secret can be reissued only by unbinding and binding again.
#WordPress: sapport-connector-wp 1.1.1
Status: Available.
#Requirements
| WordPress | 6.0 and later |
| PHP | 7.4 and later (the dashboard states 7.4–8.3) |
| Cron | WP-Cron must work: heartbeat once an hour, publisher every 10 minutes |
| Network | outbound HTTPS to the cell's domain |
#Installation
- "Plugins → Add New → Upload Plugin", choose the zip, click "Install" and "Activate".
- "Settings → Walker Formulation Academy".
- Paste the binding code → "Connect".
- Click "Check connection".
#What the plugin does
| Function | Description |
|---|---|
| Beacon | the template_redirect hook |
| Loader | a tag in wp_head |
| Page context | window.sapportPage is filled in automatically; you can change it with the sapport_connector_page_context filter |
| Publisher | creates posts from cloud jobs; an author (Author/Editor), a post type, allowed categories, and the mode ceiling are assigned; the job signature (Ed25519) is verified |
| Indexing | WordPress Core, Yoast, Rank Math, and AIOSEO sitemaps; IndexNow (the key file requires "pretty" permalinks) |
#Specifics
| Topic | What to consider |
|---|---|
anonymous mode | the plugin sends the path and referrer without the query string, so UTM from the server beacon is lost; UTM will come only from the JS counter |
| Multisite | works per site; multisite with subdirectories is not supported |
| Auto-update | not performed, update manually |
| External services | IndexNow sends URLs to api.indexnow.org |
#Verification, update, removal
- Verification: "Check connection" → success; the dashboard state is
bound;loader.js?k=…is present in<head>. - Update: upload the new zip over the old one (there is no auto-update).
- Removal: first "Disconnect" in the dashboard, then deactivate and delete the plugin.
#Common problems
| Symptom | Cause and solution |
|---|---|
| The heartbeat does not arrive | WP-Cron is disabled or the site has no traffic: set up a system cron for wp-cron.php |
No UTM in anonymous mode | expected, see above |
| IndexNow does not work | "pretty" permalinks are disabled |
#FastAPI: sapport-connector 1.0.0 (Beta)
Status: Beta. The package is built and documented, but the dashboard marks it "coming soon / Package in preparation"; there are no files on the download page yet.
#Requirements
Python 3.11 or later, PostgreSQL (the same database that holds the site's articles), outbound HTTPS to the cell's domain. The module opens no inbound addresses for the cloud.
#Installation and binding
# 1. Verify the checksum of the file from the dashboard
sha256sum sapport_connector-1.0.0-py3-none-any.whl
# 2. Install
pip install "./sapport_connector-1.0.0-py3-none-any.whl[postgres]"
# 3. Create the module's tables
sapport-connector migrate
# 4. Bind the domain with the code from the dashboard
SAPPORT_SITE__RU_MAIN__HOST=example.ru \
sapport-connector bind --site ru_main --code RU-XXXXXXXXXX \
--secret-file /run/secrets/sapport_ru_main --env-file /etc/sapport/sapport.env
# 5. Self-check: all lines show [OK]
sapport-connector selftest
# 6. Start the worker (systemd/supervisor) or cron: sapport-connector run-once
sapport-connector worker
CLI commands: bind, migrate, selftest, run-once, status, worker, unbind (irreversible). The name on PyPI is taken by a placeholder; install only the archive from the dashboard.
#Environment variables
| Variable | Required | Meaning |
|---|---|---|
SAPPORT_SITES | yes | comma-separated binding keys, for example ru_main |
SAPPORT_SITE__<KEY>__HOST | yes | domain without scheme and port |
SAPPORT_SITE__<KEY>__CELL | yes | ru | en | id |
SAPPORT_SITE__<KEY>__ID | after bind | site_id |
SAPPORT_SITE__<KEY>__SECRET_FILE | yes (worker) | path to the secret file with 0600 permissions |
SAPPORT_SITE__<KEY>__PUBLISHER | no | on enables the publisher (default off) |
SAPPORT_SITE__<KEY>__CEILING | no | mode ceiling: draft (default) or publish |
SAPPORT_DATABASE_URL | yes | PostgreSQL connection string |
SAPPORT_ADAPTER | for the publisher | module:factory returning an adapter for your articles |
The secret is written to a file, not to an environment variable. The full list and the article adapter contract are in the package documentation (README, INTEGRATION, ADAPTER, PROTOCOL, ERRORS, ACCEPTANCE, SECURITY, CSP, OPERATIONS, COMPATIBILITY, TROUBLESHOOTING).
#What you need to write
The publisher requires an adapter for your articles table (contract: ADAPTER), and covers require a media storage class on storage shared between replicas. Without media storage, articles are published without a cover. While the publisher is off, the module only maintains the connection and the heartbeat.
#Verification
sapport-connector status and selftest; the site is bound in the dashboard. For detailed diagnostics of the web process, mount healthz_router() and close the address on the reverse proxy.
#Nuxt: @sapport/nuxt-connector 1.0.0 (Beta)
Status: Beta. The package is not published.
The Nuxt part works together with the Python worker: the worker binds the site, and Nuxt receives only a derived beacon key. That key cannot be used to fetch jobs or unbind the site.
#Requirements
Nuxt 3.13+ or 4.x in Node server mode (Nitro 2, Node 18.17+; for Nuxt 4, Node 20+). Nuxt 5 is not supported.
#Installation
sha256sum sapport-nuxt-connector-1.0.0.tgz
npm install ./sapport-nuxt-connector-1.0.0.tgz
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sapport/nuxt-connector'],
sapport: {
exclude: ['/admin'],
trustedProxies: ['127.0.0.1'],
},
});
The key is not written to nuxt.config. Environment variables of the Nuxt server:
SAPPORT_SITES=ru_main
SAPPORT_SITE__RU_MAIN__HOST=example.ru
SAPPORT_SITE__RU_MAIN__CELL=ru
SAPPORT_SITE__RU_MAIN__ID=<site_id>
SAPPORT_SITE__RU_MAIN__BEACON_KEY_FILE=/run/secrets/beacon/sapport_ru_main.beacon
NUXT_SAPPORT_DIAGNOSTICS_TOKEN=<a random string of at least 16 characters>
Run the worker and Nuxt under different OS users: the root secret file must not be readable by the Nuxt process.
#Usage in pages
<script setup lang="ts">
useSapportPage({ type: 'article', id: article.id });
const consent = useSapportConsent();
</script>
<template>
<button @click="consent.grant()">Accept</button>
<button @click="consent.deny()">Decline</button>
</template>
#Diagnostics
curl -s -H "Authorization: Bearer $NUXT_SAPPORT_DIAGNOSTICS_TOKEN" https://your-site/_sapport/health
Expect "ok": true, config.state: "valid" on the binding, and beacon.sent ≥ 1 after the first view.
#Limitations
| Limitation | Consequence |
|---|---|
SPA transitions (NuxtLink) make no request to the server | the server beacon does not see them; it sees the first load and reloads |
| Prerender/SSG, ISR/SWR, CDN and nginx cache | visits served from cache are not visible |
| Static hosting without Node | no beacon, only the loader's JS counter remains |
| No signed configuration | everything is off (fail closed); the first render waits for the cloud for up to 1 s |
| Kill switch in the dashboard | takes effect within 1 hour (the lifetime of the configuration snapshot) |
| Cloud limit | 1,200 hits per minute from the server address; the module limits itself to about 1,000 per minute per process |
| Queue | 200 hits, the excess is dropped; no retries |
#Connector protocol for custom modules
Status: Available for modules that you write yourself. The protocol is used by the four ready-made modules; its list of methods may be extended compatibly.
Address: https://<cell domain>/api/v1/connector/…. All requests except bind are signed with HMAC.
#Request signature
Headers:
| Header | Value |
|---|---|
X-Sapport-Site | site_id |
X-Sapport-Ts | unix time in seconds |
X-Sapport-Nonce | 16–32 characters [A-Za-z0-9], single-use |
X-Sapport-Sig | hex HMAC-SHA256 of the string to sign |
String to sign (five lines joined by \n):
<ts>
<nonce>
<METHOD>
<path without the query string>
<sha256hex(request body)>
- The permitted clock skew is ±300 seconds.
- A nonce is stored for 600 seconds; a repeat gives
401. If the nonce store is unavailable, the response is503. - The HMAC key is the root binding secret. For
track/visitandbeacon-config, a derived beacon key is allowed:
beacon_key = hex(HMAC-SHA256(secret, "sapport-beacon-v1:" + site_id))
Test vectors for signatures and jobs are shipped with the modules (tests/_vectors/, files signing-vectors.json and job-vectors.json). Do not copy secrets from documentation examples into working systems.
#Methods
| Method and path | Signature | Purpose | Frequency and limits |
|---|---|---|---|
POST /connector/bind | no | exchange a binding code for the site credentials | 10 per minute per address; body ≤ 4096 bytes |
POST /connector/heartbeat | yes | state, receiving the configuration | once an hour; 120 per minute; body ≤ 512 KiB |
GET /connector/jobs | yes | publishing jobs | 120 per minute; not idempotent |
POST /connector/result | yes | job outcome | 120 per minute |
POST /connector/unbind | yes | unbinding ({"reason":"manual"|"uninstall"}) | 120 per minute |
GET /connector/beacon-config | yes (beacon key allowed) | beacon and loader settings | 120 per minute; Cache-Control: no-store |
POST /track/visit | yes (beacon key allowed) | server hit | 1,200 per minute per address |
GET /connector/loader.js?k= | no | loader | 5-minute cache |
#bind
POST /api/v1/connector/bind
Content-Type: application/json
{
"code": "RU-XXXXXXXXXX",
"site_url": "https://example.ru",
"cms": "fastapi_nuxt",
"module_version": "1.0.0"
}
| Field | Type | Required | Constraints | Example |
|---|---|---|---|---|
code | string | yes | the binding code; a prefix from another cell gives 404 code_not_found | RU-XXXXXXXXXX |
site_url | string | yes | the site address; the host is taken from it | https://example.ru |
cms | string | yes | bitrix | wordpress | fastapi_nuxt | bitrix |
module_version | string | no | module version | 1.2.1 |
cms_version, php_version, charset | string | no | environment details, short strings | 8.2.0 |
Response 200:
{
"site_id": "00000000-0000-4000-8000-000000000000",
"public_key": "0123456789abcdef0123456789abcdef",
"secret": "EXAMPLE_SECRET_SHOWN_ONCE",
"beacon_key": "0000000000000000000000000000000000000000000000000000000000000000",
"api_base": "https://formula-cream.pro/api/v1",
"loader_url": "https://formula-cream.pro/api/v1/connector/loader.js?k=0123456789abcdef0123456789abcdef",
"config": { "tracker": true, "behaviour": false, "chat": false, "consent_mode": "wait", "server_beacon": "full" },
"disabled": false
}
secret is returned once. Errors: 400/413 bad_request, 404 code_not_found (the code is unknown, expired, used, or belongs to another cell), 409 domain_mismatch, 429 rate_limited, 500 internal.
#heartbeat
Response 200: { "config": {…}, "min_module_version": "1.2.1", "disabled": false, "pending_jobs": 0 }. A 403 response with { "disabled": true } means the site is disabled.
#beacon-config
{
"tracker": true,
"server_beacon": "full",
"disabled": false,
"consent_mode": "wait",
"loader_url": "https://formula-cream.pro/api/v1/connector/loader.js?k=0123456789abcdef0123456789abcdef",
"public_key": "0123456789abcdef0123456789abcdef",
"ttl_seconds": 3600
}
Cache the snapshot for ttl_seconds.
#track/visit (server hit)
The body fields are described on the JS/PHP tracker page. Specifics of the signed call:
- The hit's host is taken from the binding registry. If it does not match the registered domain, the response is
400. server_beacon=off, a disabledtracker, or a tenant without access give403.anonymousmode nulls the IP and User-Agent hashes, the UA sample, and the country.- The token's daily quota gives
204without recording.
#jobs and result
Jobs are signed with Ed25519 (the kid field with a cell prefix ru-/en-/id-, canonical JSON with keys sorted in UTF-16 order). The response to GET /jobs is not idempotent: a job that was issued but not confirmed is issued again after 15 minutes. The error 503 signing_unavailable means job signing is temporarily unavailable.
result accepts the status applied, skipped, conflict, or failed. The first outcome submitted is final.
| Limit | Value |
|---|---|
| Article HTML | 2,000,000 bytes |
| Job payload | 3,000,000 bytes |
/jobs response | 4 MiB |
| Cover | 5 MiB, up to 12,000 px, up to 50 MP |
| Signature age | up to 7 days; an issued_at more than 5 minutes in the future is rejected |
Article limits are the same for the cloud and the modules: HTML up to 2,000,000 bytes, job payload up to 3,000,000 bytes. See Articles for details.
#Recommendations for a custom module
- Sign the body with exactly the bytes you send.
- Treat a server hit as statistics: do not retry on error, and do not block the page response (a reasonable timeout is 150–200 ms).
- Do not give the root secret to the frontend: the derived key is enough for the beacon.
#Open questions
- Technical. FastAPI and Nuxt: the actual presence of files in the distribution directory has not been verified; the dashboard marks the module "coming soon". Until the packages are published, the pages remain Beta.
- Technical. Bitrix module,
anonymousmode: WordPress nulls the query string on its own side, while infullmode the cloud reads UTM from the query. A single rule for all modules needs to be decided. - For the owner. The
cmsvalue for custom modules: onlybitrix,wordpress, andfastapi_nuxtare allowed. How to register a module for another platform is not defined. - Technical. Settings path in Bitrix: the module README names the dashboard section "site connections" without a path; here the path from the interface, "Promotion → System → Sites (module)", is used.