Ingestion & webhooks
Send events from a server or headless stack, and receive signed webhooks. These endpoints authenticate with your public site_id (the same identifier the browser tag ships) — they are the trust boundary for client-originated data, so they are rate-limited and bound-checked.
Identify
Attach a known identity to a session server-side.
| Parameter | Type | Description |
|---|---|---|
| session_id required | string | The visitor session to attach the identity to (axo.getSessionId() in the browser). |
| visitor_id optional | string | The persistent visitor ID (axo.getVisitorId(); the vt_sid cookie). Strongly recommended: it keys the profile server-side so traits land on the profile the visitor's sessions link to, not on a visit-scoped one. |
| site_id required | string | Your public site identifier. |
| traits required | object | At least one trait (email, external_id, or any custom attribute). Values are strings. |
curl -sS https://axoapp.ai/api/identify \
-H "Content-Type: application/json" \
-d '{
"session_id": "sess_456uvw",
"visitor_id": "vis_789xyz",
"site_id": "YOUR_SITE_ID",
"traits": { "email": "customer@example.com", "plan": "pro" }
}'Convert
Record a conversion and fan it out to configured ad platforms (Meta / Google CAPI).
| Parameter | Type | Description |
|---|---|---|
| session_id required | string | The converting session. |
| site_id required | string | Your public site identifier. |
| revenue required | number | null | Conversion revenue, 0–10,000,000. Null records a non-revenue conversion. |
| line_items optional | LineItem[] | Up to 100 items ({ sku, qty, price }); powers CAPI content_ids and the co-purchase recommender. |
curl -sS https://axoapp.ai/api/convert \
-H "Content-Type: application/json" \
-d '{
"session_id": "sess_456uvw",
"site_id": "YOUR_SITE_ID",
"revenue": 149.00,
"line_items": [{ "sku": "SKU-1", "qty": 2, "price": 24.0 }]
}'Lead capture
Identify the profile and (optionally) kick off follow-up. Feeds the Leads pipeline.
| Parameter | Type | Description |
|---|---|---|
| site_id required | string | Your public site identifier. |
| visitor_id optional | string | Persistent visitor ID to attach the lead to (preferred). |
| session_id optional | string | Alternative to visitor_id when only the session is known. |
| email / name / phone optional | string | Contact fields (email ≤ 320, name ≤ 200, phone ≤ 40 chars). None is enforced server-side, so validate before posting – a lead with no contact value is stored but cannot be worked. |
| appointment_slot optional | ISO 8601 string | Optional booked appointment time (surfaces on the Leads page). |
| source optional | string | Pass 'optin_form' to file the capture as a newsletter/opt-in; anything else (or omitted) files it as a lead form. |
curl -sS https://axoapp.ai/api/lead/capture \
-H "Content-Type: application/json" \
-d '{
"site_id": "YOUR_SITE_ID",
"visitor_id": "vis_789xyz",
"email": "lead@example.com",
"name": "Jordan Lee",
"appointment_slot": "2026-07-20T15:00:00Z"
}'Limits
The behavioural ingest endpoints /api/events and /api/score accept custom keys verbatim — extra properties on an event, extra signal fields on a score — and store them as-is. Because those endpoints authenticate with the public site_id, each request body is capped at 64 KB.
Oversized bodies are rejected, never truncated: you get 413 payload_too_large with the observed size and the limit in the message, plus X-AXO-Body-Limit and X-AXO-Body-Size response headers. Nothing from a rejected request is stored, so a clipped payload is always visible as a failed request rather than as quietly missing fields.
64 KB is the same ceiling the browser tag already works under – both navigator.sendBeacon and fetch with keepalive cap payloads there – so the tag itself can never trip this. If you are posting to these endpoints from a server, keep bodies under the cap or split them across requests.
Webhooks
Configure a webhook destination in Admin → Integrations with your endpoint URL and a secret. Always set a secret: if none is configured, deliveries are signed with your public site_id, which anyone who has seen your install snippet can reproduce. Every delivery is a JSON POST with two headers: X-VibeTag-Signature, the hex HMAC-SHA256 of the raw body keyed by your secret, and X-VibeTag-Event, the event type. Verify the signature before trusting the body. Deliveries time out after 5 s; a non-2xx response is logged as a failed delivery in the integration's health log. Endpoints must be publicly routable (private and loopback addresses are refused).
| Event type | Type | Description |
|---|---|---|
| profile.identified optional | event | Fired when an anonymous session is linked to a known identity. |
| segment.changed optional | event | Fired when a profile's primary segment changes. |
| conversion optional | event | Fired on a recorded conversion, with revenue and line items. |
| add_to_cart / abandon_cart optional | event | Cart lifecycle events, when the site emits them. |
| pageview / click optional | event | High-volume behavioral events. Subscribe deliberately — one delivery per event. |
| experiment.ended optional | event | Fired when a layout test ends (scheduled end, significance, or by hand), with per-arm results. |
| experiment.shipped optional | event | Fired when a layout test's winning arm is shipped live for everyone. |
| experiment.guardrail_paused optional | event | Fired when a Core Web Vitals guardrail pauses a layout test automatically. |
| variant.watchdog_disabled optional | event | Fired when the variant watchdog disables a variant that keeps failing to render. |
| trigger.fired optional | event | Fired when a triggered message is actually sent (successful fires only; ids, no contact values). |
| lead.captured optional | event | Fired when a lead is captured, with lead id + form id. Distinct from profile.identified; carries no PII values. |
| call.completed optional | event | Fired when an AXO Voice call is mapped into Pipeline, with call id, outcome, and the parsed callback date/time. No caller values or transcript. |
| generation.finished optional | event | Fired when a Page Engine generation lands a new revision. |
| crawl.completed optional | event | Fired when a sitemap crawl completes, with URL counts. |
| campaign.launched / campaign.paused / campaign.closed optional | event | Campaign lifecycle. Launch and pause payloads carry per-component step outcomes; close carries the outcome snapshot. Never PII. |
| stall.found optional | event | Fired by the weekly stall pass when a cohort (high-intent visitors or a segment) stalls at a step: the step, cohort key and label, both pass rates, sessions and stalled sessions per week, value per week when known, the proposed action as text (a suggestion awaiting human approval) and the ids of the inbox row and the experiment proposal it filed. One per site, step, cohort and ISO week. Never PII. |
// POST to your endpoint
// Headers: X-VibeTag-Signature: <hex hmac-sha256 of the raw body>
// X-VibeTag-Event: profile.identified
{
"event": "profile.identified",
"site_id": "site_abc123",
"visitor_id": "vis_789xyz",
"session_id": "sess_456uvw",
"profile": { "email": "customer@example.com", "external_id": "usr_12345" },
"segment": "deliberator",
"timestamp": "2026-07-14T14:32:01Z"
}