Docs

Server-side conversions

A conversion in 3x is a business outcome – a lead, a booked meeting, a confirmed WhatsApp contact – that should be reported to the ad platforms that paid for the visit. 3x records it with the session's click ids and campaign, hashes the contact data, and uploads it from the server to Meta (Conversions API), LinkedIn (Conversions API), Google Ads (API or scheduled CSV feed) and TikTok (Events API). Browser pixels are not needed.

The pieces

PieceWhat it is
Conversion definitionWhat counts: key, name, trigger, value, and per platform the event name, conversion rule or conversion action (Definitions).
DestinationWhere it goes: per platform credentials (encrypted), mode (live, test, dry_run) and transport (direct, composio, feed).
Conversion rowOne occurrence: event id, session, click ids, UTMs, value, hashed identifiers, per-platform upload status.

How a conversion is created

PathOriginTypical use
POST /v1/projects/{project}/conversions (secret key, conversions:write)serverYour backend after a form or checkout; @3x-engine/sdk/server
POST /v1/projects/{project}/leads with conversionserverLead and conversion in one call
MCP conversions_markmcpA person confirms a lead that arrived by WhatsApp or phone; matching by session or time window
Definition trigger event, goal or leadwebAutomatic, when the browser sends the event, the session reaches the goal or identify() creates a lead
POST /v1/cb beacon (publishable key), only for definitions with trigger beaconwebBrowser-side booking confirmations
DashboarddashboardManual entry

Web-originated conversions come from a public key and are therefore guarded: the session must exist, be engaged and not be test traffic; at most one conversion per session and definition; a daily cap per project (default 500); and definitions can require server confirmation – then web rows are stored as held and only uploaded once your server confirms them.

Identity and privacy

  • Contact data (e-mail, phone, first and last name, external id) is normalized per platform rules and hashed with SHA-256 on receipt; plaintext is never stored. Phone numbers without a country code use the project's country.
  • Click ids (gclid, gbraid, wbraid, fbclid, li_fat_id, ttclid) and UTMs are copied from the session.
  • Request context (IP, user agent, _fbp/_fbc/_ttp, company) is kept only while an upload is pending, at most 7 days, then erased.
  • The API never returns hashes, IPs or user agents – only which identifiers exist.

Deduplication

Every conversion has an event id (8–100 characters [A-Za-z0-9_.:-]). It is unique per project: sending the same id twice returns the existing row and never uploads twice. Use the same id in a browser pixel and the server event so Meta and LinkedIn deduplicate them, too. Automatic triggers use deterministic ids (<pageview id>:<n>, goal:<session>:<key>, lead:<lead id>).

Upload status and retries

Each platform has its own status: pending, uploaded, feed (listed in the Google CSV feed), failed (will be retried), skipped (not sendable – no identifier, too old, test traffic), not_configured, dry_run, error (rejected by the platform, not retried). The overall status is held, pending, uploaded or skipped.

Failed uploads are retried with backoff 5 min → 30 min → 2 h → 6 h → 24 h, at most 6 attempts, for conversions younger than 90 days. conversions_retry (MCP) or POST …/conversions/{id}/retry re-sends immediately.

Never from development by accident

Uploads only go out for real when the destination is in live mode and the deployment allows it (X3_ENV=production; X3_CONVERSIONS_MODE caps it; X3_CONVERSIONS_DISABLED=1 is a kill switch). Everywhere else payloads are built and a PII-free summary is logged – see Test modes.