Create a project
Sign in, create a workspace, and add your first project. You get a public key (pk_live_…) and a secret key (sk_live_…). The secret is shown once.Add the snippet
Paste this just before</head>.index.html<script defer src="https://www.attruly.com/a.js" data-key="pk_live_YOUR_KEY"></script>Load a page
Open your site. The Install tab in Attruly turns green within a few seconds of the first event.Or verify without opening a browser
Useful in CI, or when an agent is doing the install. A working key returns{ "accepted": 1, "dropped": 0 }. A bad key returns401.curlcurl -X POST https://www.attruly.com/api/v1/track \ -H "Content-Type: application/json" \ -H "Authorization: Bearer pk_live_YOUR_KEY" \ -d '{"event":"Install Check","anonymousId":"install-check"}'
Documentation
Attruly records what visitors do, resolves those events to people, and measures every channel, campaign, device and OS version against every milestone you define. Two minutes to install, and everything after that. If something here is wrong or missing, tell us and we will fix it.
Quickstart
Add one script tag. Pageviews start recording immediately, including on client-side route changes.
Browser SDK
Under 4KB gzipped, no dependencies, and it never throws into your code.
Configuration
Everything is a data attribute on the script tag.
| Attribute | Default | What it does |
|---|---|---|
| data-key | Required | Your public key. |
| data-host | https://www.attruly.com | Ingest origin. Defaults to where the script was served from. |
| data-auto-pageview | true | Set to false to send pageviews yourself. |
| data-session-timeout | 30 | Minutes of inactivity that end a visit. Match your project setting. |
| data-respect-dnt | false | Set to true to honour the browser Do Not Track signal. |
| data-exclude | — | Comma-separated path prefixes that are never tracked. |
| data-debug | false | Log events to the console instead of sending them. |
Methods
// Record something a person did.
attruly.track('Demo Booked', { plan: 'growth', seats: 12 })
// With money attached. Use a decimal amount, not cents.
attruly.track('Purchase', { orderId: 'A-1042' }, { revenue: 149.00, currency: 'USD' })
// Attach your own id. Everything this browser did before now merges into it.
attruly.identify('user_8412', { email: 'sam@acme.com', name: 'Sam Mokoena' })
// A manual pageview, when auto-pageview is off.
attruly.page({ section: 'pricing' })
// Forget this browser. Call on sign-out on a shared device.
attruly.reset()What it captures automatically
- Pageviews, including SPA route changes through the History API.
utm_source,utm_medium,utm_campaign,utm_termandutm_content.- Ad click ids:
gclid,gbraid,wbraid,fbclid,msclkid,ttclid,li_fat_id. These are captured even when the campaign carried no UTM parameters at all, which is what makes a forgotten tracking template still attributable. - The referrer, unless it is one of your own domains.
- Device, browser and operating system, parsed from the user agent.
Offline and closing tabs
sendBeacon, which is the only transport a browser guarantees to attempt while a tab is closing.Server SDK
For the events a browser never sees: webhooks, backend purchases, refunds, CRM stage changes. These are the events that carry the money.
npm install @attruly/nodeimport { Attruly } from '@attruly/node'
const attruly = new Attruly({
apiKey: process.env.ATTRULY_SECRET_KEY,
// Buffer and send in batches. Call flush() before the process exits.
batch: true,
})
// A Stripe webhook. Still attributed to the campaign that brought this
// person in, months earlier.
await attruly.track({
userId: invoice.customer,
event: 'Subscription Paid',
revenue: invoice.amount_paid / 100,
currency: invoice.currency.toUpperCase(),
// Pass your own id so a retry is stored once.
messageId: `invoice_${invoice.id}`,
})
await attruly.flush()Link the browser session, or the conversion is unattributedImportant
userId we have never seen creates a new person with no browsing history, and the conversion is reported as having no source. Call identify at signup with the browser's anonymous id, or pass anonymousId alongside the userId. Read it in the browser with window.attruly.getAnonymousId().Failure behaviour
Errors are reported through onError rather than thrown, so a checkout never fails because analytics is down. Transient failures are retried with exponential backoff and jitter. Set throwOnError: true to handle them yourself.
Identity
The hard part of attribution is not the arithmetic. It is knowing that the anonymous browser from March and the signed contract from May are the same person.
Attruly keeps an alias graph. Every identifier it has ever seen for a person — the browser's generated id, and your own user id — points at one profile.
- Call
identify()the moment you know who somebody is. Everything that browser did beforehand merges into them. - When the same person is identified on a second device, the two profiles merge. The identified profile survives, the earlier first touch is kept, and every conversion is re-credited in the background.
- Reserved traits —
email,name,phone— are promoted to searchable columns. Everything else is stored as JSON on the profile.
Goals and revenue
A goal turns a stream of events into the numerator of every rate in the product.
- Event goals match on the event name, exactly or by pattern.
- Pageview goals match on the path —
/thank-you. - Revenue goals match any event carrying money.
Give a goal a default value so a lead form is worth something even though it never charges a card. When an event carries its own revenue, that wins.
Changing a goal does not rewrite history
Funnels
A funnel is an ordered set of goals. Every segment is measured against all of its stages at once, which answers how far the people a campaign brought in actually got rather than how many of them converted once.
Create one from the Funnels screen once the project has at least two goals. A funnel takes between 2 and 8 steps. Each step points at a goal, can carry a shorter label for use inside this funnel, and can be marked as an outcome — the stage where money changes hands.
How the cohort is counted
- A person enters the funnel the first time they ever convert on the first step. That entry fixes which reporting window they belong to; converting on the first step again later does not move them.
- Later stages are counted as of now, or within the funnel's window of that person's entry. A cohort that entered this week has had less time to reach the last stage than one that entered three months ago, so a recent window always reads less converted than an older one.
- Stages are ordered, not nested. Someone can reach the fourth stage without ever matching the third. The report shows that as a gain on the stage, with the count named, rather than clamping it down to the stage above.
The window
A funnel with no window measures every later stage as of now, which suits a signup-to-revenue journey that legitimately runs for months. Setting a window of 1 to 365 days restricts later stages to that many days after entry, which is what the question "did they finish checkout" needs — a purchase six weeks later is a different visit and a different intent.
Reading the report
The breakdown groups by any of the reporting dimensions, and each person's entry is credited to their first or last touch. The Funnels URL carries the whole selection — funnel, range, dimension and touch — so one view can be linked to directly.
Highest and lowest yield compare a segment's entry-to-stage rate against the whole cohort's rate at the same stage, and rank by how much traffic the gap covers. A segment with fewer than 10 entrants is never called good or bad, because a rate built on four signups is not a finding.
Editing a funnel does not rewrite history
How attribution works
Every report takes a model. Switching model recomputes the whole page from the same underlying data.
Touches
A touch is recorded when a visit starts and carries an acquisition signal — a UTM parameter, an ad click id, or an external referrer. A visit with none of those records an explicit direct touch, which is what lets the product tell you honestly what share of your conversions it cannot attribute.
Reloading the same tagged landing page does not create a second touch. Only a genuinely different campaign fingerprint does.
Channels
Free-text sources are folded into 14 fixed channels, so Google / Paid, google / cpc and an untagged gclid all land in Paid Search.
Windows
A conversion looks back over the project's attribution window (90 days by default) and credits the touches inside it. Changing the window re-credits every conversion in the project, which happens in the background.
HTTP API
Every SDK is a thin wrapper over these three endpoints. You can call them directly.
Authentication
Send the key as a bearer token, in the X-Attruly-Key header, or as writeKey in the body. Public keys may only write events; secret keys may also read reports.
POST /api/v1/track
curl -X POST https://www.attruly.com/api/v1/track \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-d '{
"event": "Demo Booked",
"userId": "user_8412",
"properties": { "plan": "growth" },
"revenue": 149.00,
"currency": "USD",
"messageId": "demo_8412_2026_06_15"
}'POST /api/v1/batch
Up to 100 events per request, under { "events": [...] }. This is what the browser SDK posts.
POST /api/v1/identify
Attaches your id to a person and merges their anonymous history into it. Takes userId, optionally anonymousId, and traits.
Responses
A successful call returns { "accepted": 1, "dropped": 0 }. Events deliberately not recorded — bot traffic, an excluded IP, an origin that is not on your allowlist, or an exhausted quota — come back as 200 with a reason, so your site never shows an error to its own visitors because of us. A malformed body or a bad key returns 400 or 401, because those mean a broken install.
Limits
| Attribute | Default | What it does |
|---|---|---|
| Request body | 256 KB | Larger requests are rejected. |
| Events per batch | 100 | Split larger batches. |
| Properties per event | 100 | Nested objects are not accepted. |
| Backdating | 7 days | Older timestamps are clamped to the server clock. |
| Rate limit | 600 req/min per project | Batch requests: 120/min. |
Privacy and compliance
Attruly is first-party by design. Your domain, your data, your database.
- No cross-site tracking, no device fingerprinting, no third-party cookies.
- The visitor id is randomly generated and first-party. It is meaningless on any other site, including other Attruly customers'.
- IP addresses are used to resolve a country and apply your exclusion list, then discarded. The address is not stored on the visit.
- The AI analysis reads aggregate figures only. No email address, name, IP address or individual profile is ever sent to a model provider.
- Set
data-respect-dnt="true"to honour the browser Do Not Track signal, and usedata-auto-pageview="false"to gate tracking behind your own consent banner.
The full detail is in the privacy policy, including the sub-processor list and the data processing terms.