Docs

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

FieldTypeNotes
siteIdstring · requiredThe site's UUID, from the install screen.
eventstring · required1–64 characters: letters, digits, space and _ . : / -. Whitespace is trimmed and collapsed.
urlstringhttp(s) URL, ≤ 2048 characters. Optional for the API; its hostname must belong to the site. UTM parameters in it are extracted automatically.
referrerstringURL, ≤ 2048 characters. Referrers on the site's own hostnames are discarded (internal navigation is not a referral).
domainstringOptional. Registered domain, cross-checked against the site like the tracker's data-domain.
durationMsnumberNumber of milliseconds, rounded and clamped to 0–86,400,000.
propertiesobjectA 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.
revenuenumberNumber 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" } }:

StatuscodeWhen
400bad_inputMalformed JSON, bad event name, invalid url/properties/revenue; message says which.
401unauthMissing/invalid write key or unknown site.
403host_not_allowedurl (or domain) hostname is not the site's domain / an allowed host.
413too_largeBody over 16 KB.
429rate_limitedSlow down (Retry-After: 1).
500db_errorThe event could not be written to the database. Safe to retry.
500internalUnexpected server error. Safe to retry.

Validation & limits

CheckBehaviour
Rate limit20 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-listThe 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 size16 KB maximum.
BotsOn /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).
EnrichmentThe 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.