Skip to Content
Integrations & TrackingServer-Side Events (S2S)

Server-Side Events (S2S)

The browser pixel (s.js) is the easy way to record on-page events, but it’s structurally blockable: Brave, ad blockers, and privacy modes drop it, and it can’t run at all inside a native mobile app. Server-side events are the durable alternative — your own backend sends the event straight to Synaptyx over HTTPS, authenticated with an API key.

It’s the server-to-server counterpart of the pixel, and it lands in the same pixel-event store — so these events show up in your funnels, journeys, and source analytics next to the browser ones.

Server-side events never create a conversion. They are best-effort analytics signals only. Revenue stays attributed exclusively through your Revenue Sources (Stripe / Polar) and CPA postbacks, which are signature-verified and can’t be inflated by a caller. The value you send here is an unverified signal, not money.

Server-side events are included, not billed — only tracked clicks count toward your plan’s allowance. They do sit under a fair-use ceiling (20x your click allowance); past it, accepted: true, recorded: false is returned with reason: "fair_use_ceiling" until the next month.

When to use it

  • Mobile apps — no browser, no pixel. Send signup, login, checkout from your app’s backend.
  • Ad-blocked web traffic — mirror your key pixel events server-side so they survive Brave / uBlock.
  • Backend-only milestones — events that only your server knows about (trial converted, subscription renewed, KYC passed…).

Authentication & scope

Send the event with an API key carrying the write:events scope (the only write scope on the v1 API — available on Pro and Scale).

curl -X POST https://app.synaptyx.pro/api/v1/events \ -H "Authorization: ApiKey syx_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "click_id": "V1StGXR8_Z5jdHi6B-myT", "event_name": "checkout", "value": 49.9, "currency": "EUR" }'

Create a key in Settings → API Keys and tick write:events (under Advanced).

The endpoint

MethodPathScope
POST/api/v1/eventswrite:events

Request body

FieldTypeRequiredNotes
click_idstringone ofThe Synaptyx click ID this event belongs to — the attribution key for paid traffic. See Carrying the attribution key.
visitor_idstringone ofThe pixel’s visitor ID — the attribution key for organic / direct traffic, which has no click. Read it from the first-party _syx_vid cookie. Requires campaign_id.
campaign_idstringwith visitor_idThe campaign the event belongs to — the ID already in your pixel snippet URL. Ignored when click_id is supplied (the click decides).
event_namestringyes^[a-zA-Z][a-zA-Z0-9_]{0,63}$. e.g. login, signup, checkout.
valuenumbernoA numeric signal value (not revenue). Finite; clamped to ±1e12.
currencystringnoISO-4217, uppercase (EUR, USD). Ignored if malformed.
propertiesobjectnoCustom fields. Max 20 keys, primitive values only (string ≤256 chars / finite number / boolean). Anything else is silently dropped.
event_idstringnoIdempotency key — a repeat within 24h is accepted as a no-op.
timestampstringnoISO-8601 event time. Defaults to now; clamped to [now − 30d, now + 5m].

Which attribution key to send

An event has to attach to something. Send whichever key you have:

TrafficKeyWhy
Paid (a Synaptyx link was clicked)click_idMost precise — ties the event to the exact click, its cost and its source.
Organic / SEO / directvisitor_id + campaign_idThere was no click, so there is no click ID. The pixel’s visitor ID is the next-best stable key.

With a click_id, Synaptyx resolves the click → its campaign, and the campaign must belong to your account. campaignId / userId are derived from the resolved click, never from your payload, so you can’t inject events onto someone else’s traffic — and a campaign_id in the body is ignored. With a visitor_id, you name the campaign and we check you own it before accepting anything.

An ID that doesn’t exist and one that isn’t yours return the same 404 (click_not_found / campaign_not_found) — the API never reveals whether another account’s click or campaign exists.

Carrying the attribution key

The whole game is getting the key from the acquisition moment to the place that fires the event. Four patterns:

The click ID arrives on your landing page as a URL param (?clickid=…) or the first-party _syx_cid cookie. Persist it on the user’s record / order when they sign up or check out, then replay it from your backend:

landing ?clickid=… ──▶ store on user/order ──▶ POST /api/v1/events { click_id, … }

This is the same mechanism the Revenue Sources snippet uses.

Validation, limits & abuse controls

  • Strict shapes — the IDs and event_name are regex-validated; a bad value returns 400 (it is never stored loosely).
  • properties sanitisation — over-limit keys, non-primitive values, and oversized blobs are dropped, but the event still goes through with the valid fields.
  • Idempotency — pass event_id to make retries safe; a duplicate within 24h is a no-op.
  • Rate limits — per API key (standard X-RateLimit-* headers, see Limits) and a per-identifier cap, so one click or visitor can’t be spammed with events.

Responses

StatusMeaning
200Accepted (body: { "accepted": true }, or { "accepted": true, "deduplicated": true }).
400invalid_event_name, invalid_click_id, invalid_visitor_id, invalid_campaign_id, or missing_identifier (neither click_id nor visitor_id supplied).
403insufficient_scope — the key lacks write:events.
404click_not_found / campaign_not_found — unknown or not-owned ID.
429rate_limited — back off and honour Retry-After.

Examples

curl -X POST https://app.synaptyx.pro/api/v1/events \ -H "Authorization: ApiKey syx_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "click_id": "V1StGXR8_Z5jdHi6B-myT", "event_name": "checkout", "value": 49.9, "currency": "EUR", "event_id": "order_8421", "properties": { "plan": "pro", "seats": 3 } }'

Organic visitor (no click) — same call, keyed on the _syx_vid cookie:

curl -X POST https://app.synaptyx.pro/api/v1/events \ -H "Authorization: ApiKey syx_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "visitor_id": "k3n8v1qwz9r2mx7cms4ussbe", "campaign_id": "cmnvqgg5n03vo01n0as05a0n1", "event_name": "signup" }'

Server-side events vs. conversions

Server-side eventsConversions
EndpointPOST /api/v1/eventsRevenue webhooks / CPA postbacks
TrustUnverified signalSignature-verified
Creates revenue?NoYes
Use forFunnel steps, engagement, milestonesMoney, payouts, ROI
Survives ad blockersYes (server-side)Yes (server-side)

Next