HTTP API reference
Send events from your backend — webhooks, cron jobs, payment confirmations — with a single authenticated POST.
POST /api/track
One endpoint, one JSON body. Use it for anything that must not depend on the browser: payment webhooks, cron jobs, signup confirmations.
curl -X POST https://linqry.com/api/track \
-H 'authorization: Bearer YOUR_WRITE_KEY' \
-H 'content-type: application/json' \
-d '{
"siteId": "YOUR_SITE_ID",
"event": "subscription-renewed",
"url": "https://yourdomain.com/billing",
"properties": { "plan": "pro" },
"revenue": 4900
}'Authentication
/api/track and /api/revenue require the site’s write key, shown on the site’s Settings page. Send it as Authorization: Bearer <key> or x-linqry-key: <key>. Requests without a valid key get 401 { "error": { "code": "unauth", "message": "Invalid site id or write key" } } — the same answer for an unknown site id, so ids cannot be enumerated. The write key is a secret: keep it server-side, never in the browser snippet.
Fields
| Field | Type | Notes |
|---|---|---|
siteId | string · required | The site's UUID, from the install screen. |
event | string · required | 1–64 characters: letters, digits, space and _ . : / -. Whitespace is trimmed and collapsed. |
url | string | http(s) URL, ≤ 2048 characters. Optional for the API; its hostname must belong to the site. UTM parameters in it are extracted automatically. |
referrer | string | URL, ≤ 2048 characters. Referrers on the site's own hostnames are discarded (internal navigation is not a referral). |
domain | string | Optional. Registered domain, cross-checked against the site like the tracker's data-domain. |
durationMs | number | Number of milliseconds, rounded and clamped to 0–86,400,000. |
properties | object | A flat JSON object: at most 20 keys, keys ≤ 64 chars, primitive values; strings are truncated to 256 characters, nested values are stored as a JSON string, total ≤ 4 KB. |
revenue | number | Number of cents. Floats are rounded, values are clamped to 0–1,000,000,000. |
The endpoint equally accepts the compact wire format the browser tracker sends: sid, n, u, r, d, p, rev, dom map to the fields above in order. Use whichever reads better in your codebase; don’t mix the two forms in one payload.
Attribution note: a server-side event carries whatever url and referrer you send. If you send neither, the event has no referrer or UTM source and its revenue is reported under direct / (none). To attribute backend revenue to a channel, store the landing URL and referrer at signup and pass them here.
Responses
Success: 200 { "ok": true }. Errors use one shape, { "error": { "code", "message" } }:
| Status | code | When |
|---|---|---|
| 400 | bad_input | Malformed JSON, bad event name, invalid url/properties/revenue; message says which. |
| 401 | unauth | Missing/invalid write key or unknown site. |
| 403 | host_not_allowed | url (or domain) hostname is not the site's domain / an allowed host. |
| 413 | too_large | Body over 16 KB. |
| 429 | rate_limited | Slow down (Retry-After: 1). |
| 500 | db_error | The event could not be written to the database. Safe to retry. |
| 500 | internal | Unexpected server error. Safe to retry. |
Validation & limits
| Check | Behaviour |
|---|---|
| Rate limit | 20 events/second per IP + site with bursts to 100, plus a per-site ceiling of 100 events/second (burst 1000) that does not depend on the IP. |
| Hostname allow-list | The page URL's hostname must be the site's registered domain, one of its additional hosts, or a subdomain of either. www. is ignored on both sides. localhost / 127.0.0.1 are accepted only when the site itself is registered on localhost/127.0.0.1, or when NODE_ENV is other than production. |
| Body size | 16 KB maximum. |
| Bots | On /api/collect, requests whose user agent matches a crawler, headless browser, HTTP client (curl, python-requests…) or preview fetcher, or with no user agent, are dropped. /api/track is not bot-filtered (it is authenticated). |
| Enrichment | The server derives the daily-rotating visitor hash and 30-minute session, parses the user agent, and resolves country/region/city from the IP the trusted proxy reports. The IP is used in memory and never stored or logged. |
POST /api/revenue
A dedicated endpoint for attaching revenue to an existing visitor. Same authentication and error shape as /api/track; details and an example are on the revenue page.
/api/collect: the browser endpoint
The tracker sends its beacons to /api/collect as text/plain JSON (no CORS preflight). It always answers 204, accepted or not, so nothing about a site leaks to page scripts; misconfigurations therefore fail silently. Use the Realtime view to verify. From your own backend, always use /api/track.