Tracking & Server-Side Events
Tracking keys, the installer payloads, and the two ways a system posts an outcome it already knows about.
Two different jobs live here. Tracking keys and installers are how the
browser tracker gets onto a site. POST /api/v1/events is how a system that
already knows an outcome happened — an ERP, a POS, a back office — tells Atribu
without a browser at all.
Tracking keys
POST /api/v1/tracking/keys
GET /api/v1/tracking/keysThe POST is idempotent by natural key: a profile that already has an
active key gets that key back rather than a second one. So an agent can call it
unconditionally on every run without accumulating keys.
Installer payloads
GET /api/v1/tracking/snippet
GET /api/v1/tracking/installers/gtm
GET /api/v1/tracking/installers/shopify-pixelsnippet returns the raw <script> (optionally bundled with the Meta Pixel);
the two installers return the payload a Google Tag Manager container or a
Shopify Web Pixel expects. An agent that can edit the site puts the snippet in;
one that cannot hands the payload to the person who can.
The by-hand versions of all three, with screenshots, are in Install the tracker.
Server-side outcome events
POST /api/v1/eventsThis is how a dealer's ERP posts a closed sale. Two rules matter more than the schema:
The idempotency key is required whenever the event carries money
Without one it defaults to a fresh UUID, and a retried sale becomes a second conversion. A payment posted twice is revenue counted twice, and every ROAS on the profile is wrong afterwards.
event_type is free text. What gives a name meaning is a
conversion definition that lists it — there is no fixed
sale_closed type to look up. Post checkout_completed and then define what it
means; the definition is what decides whether it counts as cash, and whether it
is attribution-eligible.
One customer paying for several people — subject_ref
A clinic often bills several patients (children, dependants) to one customer:
the parent whose contact carried the ad conversation. By default Atribu counts
one first payment per customer, so a second patient's first treatment would
read as a repeat payment — not a new customer, not a Purchase at Meta, and
credited only to the ad that acquired the parent.
Send properties.subject_ref — your own opaque id for who the payment is
for — and each distinct subject_ref under one customer gets its own first
payment. A payment without it behaves exactly as before.
Accepted shapes: a canonical UUID (8-4-4-4-12 hex — your internal patient
uuid is ideal), or a short token of up to 32 letters, digits, _ and -.
Turn it on without inflating new customers. A subject counts as new only if
every earlier payment of that customer carries a different subject_ref; an
earlier untagged payment makes it a returning one. So before you start sending
subject_ref on new payments, re-send the customer's historical payments with
the same idempotency key plus their subject_ref — Atribu re-derives the
first-payment flags on that update. A retry that omits subject_ref removes
it from the stored payment again.
Two consequences of that rule, both deliberate:
- A household with earlier payments from an integration keeps the
per-customer rule. Payments that arrive from MercadoPago, Webpay, a CSV
import or Shopify cannot carry a
subject_ref, so they cannot be backfilled; a later patient of that household is never counted as a new customer. (At the time this shipped, no clinic customer had such a mixed history.) - An untagged payment after a tagged one is never a first payment, even within the 7 days that normally group a deposit with its balance — it may be the same patient again. Tag both payments to keep them grouped.
{
"event_name": "payment_received",
"idempotency_key": "invoice_20931",
"properties": { "value": 45000, "currency": "CLP", "subject_ref": "3f6c1e2a-8b7d-4c1e-9a55-0e2b7d9c4f11" },
"user_traits": { "phone": "+56911112222" }
}subject_ref is never personal data
Atribu rejects with 400 invalid_parameter a value that contains an @, 7 or
more digits in a row once separators are removed (a RUT/RUN with or without
check digit, even split by a letter, a phone number, a compact date), a date,
a name, or a 32/40/64 character hex digest. Those checks catch mistakes; they
cannot recognise every name (JuanPerez) or a hashed or encoded personal
identifier, so subject_ref must be an opaque id — that is your
obligation under the data-processing agreement. Atribu uses subject_ref only
to decide first payments and count new customers — it is never sent to Meta,
Google or TikTok, no API returns it, and it is erased with the customer.
Retracting an event you sent — POST /api/v1/events/retractions
A resend cannot correct a wrong row: Atribu never overwrites a customer it already resolved with "none". When a backfill or a bug ingested an event that should not exist (a payment credited to the wrong contact, say), retract it by the key you sent it with:
{ "idempotency_key": "invoice_20931", "reason": "Backfill credited the wrong contact" }One transaction deletes the event, the conversions extracted from it and their
attribution, recomputes the first-payment flag of the customer's other payments
(a second payment can become the first), queues the profile's attribution
recompute and writes an audit record naming your key and reason. The answer
is counts only. It is idempotent: the same key again answers 200 with
status: "already_retracted" and zero counts.
Not after an export
Once a conversion export exists for the event, in any status, the retraction is
refused with 409 event_already_exported and nothing is deleted: an ad
platform may already have seen it. A WhatsApp or Instagram conversation that
recorded the event as its lead refuses it too (409 event_linked_to_conversation).
POST /api/v1/payments/webpay is the same idea for Transbank Webpay, which has
no OAuth connection to authorize: the payment is reported directly. See
Webpay.
A CRM connection is not required
Readiness's crm_or_outcome_source_connected step is
satisfied by outcome events arriving on this route. A system that posts its own
outcomes needs no CRM and no browser.
| Method | Path | What it does |
|---|---|---|
POST | /api/v1/events | Ingest a server-side outcome event (e.g. a closed sale) |
POST | /api/v1/events/retractions | Retract an event you ingested |
GET | /api/v1/payments | List payments, with the merchant actions applied to each |
PUT | /api/v1/payments/{payment_id}/installment | Mark (or unmark) a payment as a later installment |
PUT | /api/v1/payments/{payment_id}/payer | Assign who paid a payment |
POST | /api/v1/payments/{payment_id}/refund | Record a refund made outside the provider |
DELETE | /api/v1/payments/{payment_id}/refund | Undo a manually recorded refund |
GET | /api/v1/payments/linkage | How each sale reached an ad («Cómo llega la plata») |
POST | /api/v1/payments/webpay | Report a Webpay (Transbank) payment |
GET | /api/v1/tracking/custom-domains | List the profile's first-party tracking domains |
POST | /api/v1/tracking/custom-domains | Register a first-party tracking domain |
DELETE | /api/v1/tracking/custom-domains/{id} | Remove a first-party tracking domain |
POST | /api/v1/tracking/custom-domains/{id}/verify | Re-check a domain's DNS and store the verdict |
GET | /api/v1/tracking/events | The raw enriched-event feed |
GET | /api/v1/tracking/installers/gtm | Get the Google Tag Manager installer payload |
GET | /api/v1/tracking/installers/shopify-pixel | Get the Shopify Web Pixel installer payload |
GET | /api/v1/tracking/keys | List the profile's active tracking keys |
POST | /api/v1/tracking/keys | Issue a tracking key (idempotent) |
DELETE | /api/v1/tracking/keys/{id} | Revoke a tracking key |
GET | /api/v1/tracking/ops | Warehouse diagnostics for the profile |
GET | /api/v1/tracking/origin | Where this profile's tracker collects |
GET | /api/v1/tracking/quality | Is this profile's tracking healthy? |
GET | /api/v1/tracking/replay-status | The profile's most recent attribution replay |
GET | /api/v1/tracking/settings | Read the profile's tracker and enrichment settings |
PATCH | /api/v1/tracking/settings | Update the profile's tracker and enrichment settings |
GET | /api/v1/tracking/snippet | Get the raw tracker snippet (with optional Meta Pixel bundle) |
GET | /api/v1/tracking/verification | The latest install-verification attempt |
POST | /api/v1/tracking/verification | Start an install verification |
Generated from openapi.json. The full request and response schema for every operation is in the OpenAPI document.