Server-side ingestion

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.

When to use thisMost sites just install the browser tag, which calls these endpoints for you. Reach for the raw endpoints for headless/SSR flows, mobile apps, or server-side conversions you can't fire from the browser.

Identify

Attach a known identity to a session server-side.

POST/api/identify
ParameterTypeDescription
session_id
required
stringThe visitor session to attach the identity to (axo.getSessionId() in the browser).
visitor_id
optional
stringThe 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
stringYour public site identifier.
traits
required
objectAt least one trait (email, external_id, or any custom attribute). Values are strings.
curl
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).

POST/api/convert
ParameterTypeDescription
session_id
required
stringThe converting session.
site_id
required
stringYour public site identifier.
revenue
required
number | nullConversion 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
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.

POST/api/lead/capture
ParameterTypeDescription
site_id
required
stringYour public site identifier.
visitor_id
optional
stringPersistent visitor ID to attach the lead to (preferred).
session_id
optional
stringAlternative to visitor_id when only the session is known.
email / name / phone
optional
stringContact 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 stringOptional booked appointment time (surfaces on the Leads page).
source
optional
stringPass 'optin_form' to file the capture as a newsletter/opt-in; anything else (or omitted) files it as a lead form.
curl
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).

Supported events
Event typeTypeDescription
profile.identified
optional
eventFired when an anonymous session is linked to a known identity.
segment.changed
optional
eventFired when a profile's primary segment changes.
conversion
optional
eventFired on a recorded conversion, with revenue and line items.
add_to_cart / abandon_cart
optional
eventCart lifecycle events, when the site emits them.
pageview / click
optional
eventHigh-volume behavioral events. Subscribe deliberately — one delivery per event.
experiment.ended
optional
eventFired when a layout test ends (scheduled end, significance, or by hand), with per-arm results.
experiment.shipped
optional
eventFired when a layout test's winning arm is shipped live for everyone.
experiment.guardrail_paused
optional
eventFired when a Core Web Vitals guardrail pauses a layout test automatically.
variant.watchdog_disabled
optional
eventFired when the variant watchdog disables a variant that keeps failing to render.
trigger.fired
optional
eventFired when a triggered message is actually sent (successful fires only; ids, no contact values).
lead.captured
optional
eventFired when a lead is captured, with lead id + form id. Distinct from profile.identified; carries no PII values.
call.completed
optional
eventFired 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
eventFired when a Page Engine generation lands a new revision.
crawl.completed
optional
eventFired when a sitemap crawl completes, with URL counts.
campaign.launched / campaign.paused / campaign.closed
optional
eventCampaign lifecycle. Launch and pause payloads carry per-component step outcomes; close carries the outcome snapshot. Never PII.
stall.found
optional
eventFired 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"
}