Server-side sessions
The advanced setup. Post page views and conversions from your backend, under one id you own. Attribution becomes exact, nothing can block it, and no JavaScript is required.
Most sites install the tracker and are done. Reach for this when your conversions happen somewhere a browser cannot reliably report: a checkout that completes server-side, a redirect gateway, a payment webhook, a native app.
The problem it solves
Your session id is minted in the browser and kept in localStorage. Your backend cannot read it. So when your server posts a conversion, it sends an id the browser never used, and we cannot connect it to a visit.
Your conversion count stays correct. What breaks is everything around it: the visit that produced the sale is missing from the denominator, so your conversion rate can read above 100%, and your per-source, per-page and per-device breakdowns cover only the fraction we could join.
The fix: one id, two events
You own the identity. Mint it yourself, a UUID in your own first-party cookie set at your edge on the first request. Do not call askbowtie to generate it: that puts our latency and our uptime in front of your page load, and you already have a cookie.
Then post both events under it.
On landing
POST https://askbowtie.com/api/ingest
{
"domain": "example.com",
"session_id": "<your visitor id>",
"events": [{
"event_type": "page_load",
"source": "server",
"page": "/pricing",
"referrer": "https://www.google.com/",
"ts": 1735000000
}]
}
On conversion, later, same id
POST https://askbowtie.com/api/ingest
{
"domain": "example.com",
"session_id": "<the same id>",
"events": [{
"event_type": "conversion",
"source": "server",
"goal": "purchase",
"value": 49,
"currency": "USD",
"page": "/checkout",
"ts": 1735000600
}]
}
That is the whole integration. No SDK, no cookie from us, no JavaScript.
What it changes
| Conversions only | Server-side sessions | |
|---|---|---|
| Conversion count | correct | correct |
| Conversion rate | can exceed 100% | correct by construction |
| Join | probabilistic, or none | exact, every time |
| Source / page / device breakdowns | attributed slice only | complete |
| Blocked by ad-blockers | page views are | no |
Rules that bite
- Every event in the batch must carry
"source": "server". That is what opts the batch out of the browser bot filter. Without it, a backend with no browser user-agent is dropped silently behind an HTTP 200. - Timestamps are clamped to server time. A
tsin the future, or more than 7 days old, is replaced by arrival time. You cannot backfill history this way. goalis effectively required on a conversion. Without it the conversion is stored asunknown_conversionand the name cannot be recovered later.- Send the
referreryourself on a server-side page view. We cannot see it, and it is what makes acquisition attribution work. - 50 events per batch maximum.
- Read the response.
{"stored":{"server":3}}tells you exactly how many events persisted. If that number is missing or short, they did not land.
Check it worked
Ask for get_traffic over MCP and look at attribution. You want unattributed_rate near zero and by_reason.attributed_session carrying the count. by_goal ranks your goals worst-wired first, so it names which integration to fix instead of making you guess.
One caveat worth knowing: attribution is window-bounded. A conversion resolves only when its page view falls inside the window you queried, so a one-hour view understates a perfectly wired site. Check at 24 hours or wider.
Using it alongside the tracker
Common, and fine. Keep the tracker for engagement, errors and performance; add server-side page views for the flows a browser cannot see. Pick one owner of the identity per flow: if both fire for the same visit under different ids, you will double-count sessions.
For AI agents
Wiring this with a coding agent? Hand it this:
Post BOTH the page view and the conversion to https://askbowtie.com/api/ingest
under ONE session_id that our backend owns (a UUID in our own first-party cookie,
set at the edge on first request). Every event must include "source": "server".
The page_load must carry "page" and "referrer"; the conversion must carry "goal".
Do not call askbowtie to mint the id. Verify by checking that the ingest response
contains {"stored":{"server":N}} with the N you sent, then that get_traffic's
attribution.by_reason.attributed_session covers your conversions.
New here? Install the tracker first. Most sites need nothing more than that.