Docs
Tracking: one script tag and one API
Two pieces tie every customer to the channel, campaign and asset that brought them in: one script tag on your site, and one API call from your server for the signups, payments and meetings your backend sees. The autopilot reads the same numbers every day and moves the budget to the cheapest customer.
1. Install the snippet
Paste one line into the <head> of every page. The cabinet prints it with your product’s key on the product’s Tracking screen.
<script async src="https://gtmsdr.com/t.js" data-key="gs_pub_YOUR_KEY"></script>The gs_pub_ key is publishable: it ships in the page. Hits count only from your product’s domain, its subdomains and the extra hosts you add in Tracking, so a copied tag counts nothing anywhere else.
| Attribute | Default | What it does |
|---|---|---|
data-key | none | Your product’s snippet key, gs_pub_…. Required. |
data-api | https://api.gtmsdr.com | Where hits go. Point it at a first-party proxy if you run one. |
data-forms | on | off stops form capture. data-gtmsdr="off" on a single <form> skips that form. |
2. What the snippet records
- A pageview on load and on every single-page navigation.
- Three first-party cookies on your registrable domain:
gs_vid, the visitor, kept 2 years and readable by your server;gs_sid, the session, closed after 30 idle minutes or by a new campaign or click id;gs_ft, the first landing page and referrer, kept 90 days. - Click ids, kept with the session:
gclid,gbraid,wbraid,msclkid,li_fat_id,rdt_cid,oppref,ttclid,twclid,fbclid. - Every
<form>with an email field, as a lead with the page, campaign and channel that brought the visitor. Password, card-number, hidden and file fields never leave the page.
Hits go to POST /api/v1/t/hit as a text/plain JSON body (no preflight), by sendBeacon with a fetch(keepalive) fallback. The endpoint answers 204 when the hit is counted or dropped as a bot, 400 for a malformed hit, 401 for an unknown key and 403 for a host outside your list.
3. Name people in the browser
Call identify when someone signs in and signup when an account is created. Calls made before the script loads wait in a queue, so the stub below can sit above the tag.
window.gtmsdr = window.gtmsdr || function () {
(gtmsdr.q = gtmsdr.q || []).push(arguments)
}
gtmsdr('identify', { email: '[email protected]', user_id: 'u-42', company: 'Acme' })
gtmsdr('signup', { email: '[email protected]', user_id: 'u-42' })
// Once t.js has loaded: the visitor id, to forward to your server as visitor_id
const visitorId = gtmsdr.visitorId()identify takes email, user_id, name, company, phone and title; signup takes the same fields.
4. Send events from your server
Your backend knows what the browser cannot: the payment that cleared, the refund, the meeting that took place. Send them to the events API with a secret key made on the Tracking screen (New key; the secret is shown once). Use Authorization: Bearer gs_evt_… or the X-Api-Key header.
curl https://api.gtmsdr.com/api/v1/events \
-H "Authorization: Bearer gs_evt_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "payment",
"id": "ch_3Q8Xr2",
"user_id": "u-42",
"email": "[email protected]",
"amount_cents": 4900,
"currency": "USD",
"visitor_id": "<gs_vid cookie>",
"occurred_at": "2026-10-15T10:00:00Z",
"properties": {"plan": "pro"}
}'| type | Needs | Records |
|---|---|---|
identify | email or user_id | Joins the person, and the visitor_id when you send it. |
signup | email or user_id | A signup, once per person. |
payment | id, amount_cents | A payment, once per id. The person’s first payment also makes them a customer. |
refund | id, amount_cents | A refund, once per id. |
meeting.held | id, email or user_id | A meeting that took place, once per id. |
Send visitor_id (the gs_vid cookie) whenever you have it: it joins the server event to the visits that led to it. The same id sent twice records once, so retries are safe.
5. Batches and answers
Send up to 100 events in one call as {"events": [...]}.
| Request | Answer |
|---|---|
| One event | 201 accepted, 200 duplicate, 400 rejected with the reason. |
| A batch | 200 with results: one {index, status, event_id, error} per event, in order. |
CORS is open to every origin on /api/v1/events and /api/v1/t/hit only.
6. Link markers
Every link the autopilot publishes carries the asset and the variant behind it, so a click names them exactly. Add the same markers to links you place yourself and they count the same way.
https://acme.io/pricing?utm_source=linkedin&utm_medium=social&utm_campaign=launch&gs_a=ASSET_ID&gs_v=VARIANT_IDgs_a: the asset id;gs_v: the variant id.utm_content=a_<asset id>also names the asset.gs_ch: forces the channel.gs_cmp: names the campaign.- A marker for an asset that is not your product’s is ignored.
7. How credit is split
Every lead, signup, paying customer and meeting is split over the person’s touches in the 90 days before it: their web sessions, and the channel events about them (an email clicked or answered, an ad clicked, a document viewed). Three models read the same touches:
position, the default: 40% to the first touch, 40% to the last, 20% across the middle. One touch takes all of it, two split it in half.first_touch: all of it to the first touch.last_touch: all of it to the last touch.
A direct visit counts only when nothing else touched the person. With no touch at all, the outcome’s own channel takes the credit, then the channel of the lead it came from. The funnel shows the share attributed, and the target is nine in ten.