Walker Formulation Academy for developers API reference Error catalog OpenAPI
Sections

#Modules for Websites

Status: Available: Bitrix sapport.connector 1.2.1 and WordPress sapport-connector-wp 1.1.1. Beta: FastAPI sapport-connector 1.0.0 and Nuxt @sapport/nuxt-connector 1.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

  1. When to use it
  2. Common to all modules
  3. Bitrix
  4. WordPress
  5. FastAPI
  6. Nuxt
  7. Connector protocol for custom modules
  8. Open questions

#When to use it

SituationSolution
The site runs on Bitrix (not cloud Bitrix24)Bitrix module
The site runs on WordPressWordPress module
The site runs on FastAPI + PostgreSQLFastAPI module (+ Nuxt for the frontend), Beta
Another platform, only a counter is neededJS tracker by token
Another platform, chat and behavior tracking are neededLoader
A custom module for a non-standard platformthe Connector protocol section

#Common to all modules

#Access

Who bindsa dashboard user with access to "Promotion → System → Sites (module)"; the tracker key is issued by the seo.manage permission
Paidno, there is no separate fee for the module
Personal dataIP and User-Agent hashes, country (in full mode); see Overview

#What the module does

CapabilityDescription
Server beaconafter 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
Loaderthe loader.js tag (counter, behavior tracking, chat) is output in <head>
Heartbeatonce an hour the module reports its state and receives its configuration
Publishertakes article publishing jobs from the cloud and applies them (as drafts by default)
Kill switchdisabling the site in the dashboard stops all parts

#How to bind a site: the binding code

  1. In the dashboard: "Promotion → System → Sites (module)" → add the domain → get a binding code.
  2. The code carries the cell prefix: RU-…, EN-…, or ID-…. It lives for 15 minutes and is single-use. A code from another cell gives the error "code not found".
  3. 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).
  4. 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.

ModuleVersionFileStatus
Bitrix1.2.1/connector/sapport.connector-1.2.1.zipAvailable
WordPress1.1.1/connector/sapport-connector-wp-1.1.1.zipAvailable
FastAPI1.0.0sapport_connector-1.0.0-py3-none-any.whl + documentation archiveBeta, not published
Nuxt1.0.0sapport-nuxt-connector-1.0.0.tgzBeta, 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_beaconWhat is sentDefault for
fullpath, referrer, User-Agent, IP, countryRU
anonymousthe same, but the cloud does not store the IP/UA hashes, the UA sample, or the countryEN, ID
offnothingn/a

#Bitrix: sapport.connector 1.2.1

Status: Available.

#Requirements

Editionany, starting from "Start" (not cloud Bitrix24)
PHP7.4–8.3 with the curl extension
Networkoutbound HTTPS to your cell's domain
For the publisherthe iblock module, the sodium extension, the CREATE TABLE privilege

#Installation

  1. Unpack the archive into /local/modules/.
  2. In the admin area, open "Marketplace → Installed solutions" (/bitrix/admin/partner_modules.php) and click "Install" next to "Sapport: connector".
  3. Get a binding code in the Walker Formulation Academy dashboard (see above).
  4. "Settings → Product settings → Module settings → Sapport: connector" → enter the code → "Bind".
  5. Click "Check connection".

Recommendations:

#What the module does

FunctionDescription
Beaconin 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
Loaderoutputs the loader.js tag
Page context\Sapport\Connector\Loader::setPage('iblock_element', $id); tells the chat which page is being discussed
Publishercreates 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
Sectionsallowed infoblock sections (up to 500)
IndexNownotifies 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

ParameterPurpose
Binding codesingle-use, from the dashboard
Site runs behind a proxytrust X-Forwarded-For
Publisher modedraft (default) or publish, if the ceiling allows
Allowed sectionswhere the publisher may create items
IndexingIndexNow, sitemap

Which parts are on (tracker, behaviour, chat, the consent mode, the beacon mode) is set in the dashboard.

#Verification

  1. The "Check connection" button → success.
  2. In the dashboard, the site state is bound.
  3. 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/.
  4. The page outputs the loader.js?k=… tag in <head>.

#Update and removal

#Common problems

SymptomCause and solution
No hits from botsBitrix 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_foundthe code has expired, been used, or is from another cell (wrong prefix)
All visitors have the same IPenable "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

WordPress6.0 and later
PHP7.4 and later (the dashboard states 7.4–8.3)
CronWP-Cron must work: heartbeat once an hour, publisher every 10 minutes
Networkoutbound HTTPS to the cell's domain

#Installation

  1. "Plugins → Add New → Upload Plugin", choose the zip, click "Install" and "Activate".
  2. "Settings → Walker Formulation Academy".
  3. Paste the binding code → "Connect".
  4. Click "Check connection".

#What the plugin does

FunctionDescription
Beaconthe template_redirect hook
Loadera tag in wp_head
Page contextwindow.sapportPage is filled in automatically; you can change it with the sapport_connector_page_context filter
Publishercreates 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
IndexingWordPress Core, Yoast, Rank Math, and AIOSEO sitemaps; IndexNow (the key file requires "pretty" permalinks)

#Specifics

TopicWhat to consider
anonymous modethe 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
Multisiteworks per site; multisite with subdirectories is not supported
Auto-updatenot performed, update manually
External servicesIndexNow sends URLs to api.indexnow.org

#Verification, update, removal

#Common problems

SymptomCause and solution
The heartbeat does not arriveWP-Cron is disabled or the site has no traffic: set up a system cron for wp-cron.php
No UTM in anonymous modeexpected, 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

VariableRequiredMeaning
SAPPORT_SITESyescomma-separated binding keys, for example ru_main
SAPPORT_SITE__<KEY>__HOSTyesdomain without scheme and port
SAPPORT_SITE__<KEY>__CELLyesru | en | id
SAPPORT_SITE__<KEY>__IDafter bindsite_id
SAPPORT_SITE__<KEY>__SECRET_FILEyes (worker)path to the secret file with 0600 permissions
SAPPORT_SITE__<KEY>__PUBLISHERnoon enables the publisher (default off)
SAPPORT_SITE__<KEY>__CEILINGnomode ceiling: draft (default) or publish
SAPPORT_DATABASE_URLyesPostgreSQL connection string
SAPPORT_ADAPTERfor the publishermodule: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

LimitationConsequence
SPA transitions (NuxtLink) make no request to the serverthe server beacon does not see them; it sees the first load and reloads
Prerender/SSG, ISR/SWR, CDN and nginx cachevisits served from cache are not visible
Static hosting without Nodeno beacon, only the loader's JS counter remains
No signed configurationeverything is off (fail closed); the first render waits for the cloud for up to 1 s
Kill switch in the dashboardtakes effect within 1 hour (the lifetime of the configuration snapshot)
Cloud limit1,200 hits per minute from the server address; the module limits itself to about 1,000 per minute per process
Queue200 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:

HeaderValue
X-Sapport-Sitesite_id
X-Sapport-Tsunix time in seconds
X-Sapport-Nonce16–32 characters [A-Za-z0-9], single-use
X-Sapport-Sighex 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)>
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 pathSignaturePurposeFrequency and limits
POST /connector/bindnoexchange a binding code for the site credentials10 per minute per address; body ≤ 4096 bytes
POST /connector/heartbeatyesstate, receiving the configurationonce an hour; 120 per minute; body ≤ 512 KiB
GET /connector/jobsyespublishing jobs120 per minute; not idempotent
POST /connector/resultyesjob outcome120 per minute
POST /connector/unbindyesunbinding ({"reason":"manual"|"uninstall"})120 per minute
GET /connector/beacon-configyes (beacon key allowed)beacon and loader settings120 per minute; Cache-Control: no-store
POST /track/visityes (beacon key allowed)server hit1,200 per minute per address
GET /connector/loader.js?k=noloader5-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"
}
FieldTypeRequiredConstraintsExample
codestringyesthe binding code; a prefix from another cell gives 404 code_not_foundRU-XXXXXXXXXX
site_urlstringyesthe site address; the host is taken from ithttps://example.ru
cmsstringyesbitrix | wordpress | fastapi_nuxtbitrix
module_versionstringnomodule version1.2.1
cms_version, php_version, charsetstringnoenvironment details, short strings8.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:

#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.

LimitValue
Article HTML2,000,000 bytes
Job payload3,000,000 bytes
/jobs response4 MiB
Cover5 MiB, up to 12,000 px, up to 50 MP
Signature ageup 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

#Open questions

  1. 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.
  2. Technical. Bitrix module, anonymous mode: WordPress nulls the query string on its own side, while in full mode the cloud reads UTM from the query. A single rule for all modules needs to be decided.
  3. For the owner. The cms value for custom modules: only bitrix, wordpress, and fastapi_nuxt are allowed. How to register a module for another platform is not defined.
  4. 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.