POST /v1/track. It runs in browsers (with
sendBeacon drain on page hide) and in Node 18+ (global fetch). It also
carries the one call that reports what a reader did with an article: see
Content interactions.
The SDK is not yet published to npm - your 4D contact provides the package
during integration onboarding. Everything it does rides on the public
/v1/track endpoint, so you can always start with
plain HTTP and adopt the SDK later without
changing anything server-side.Quick start
navigator.sendBeacon when the
page is hidden or unloading. Every event carries a client-generated UUID
event_id which the server dedupes on, so retries after network failures can
never double-count tag affinity.
Identity
The platform only records engagement for known members. Identity comes from one of:onError - call identify() as soon as you know who the user is.
Delivery semantics
track() never throws. The only error surface is the onError callback:
Content interactions
One call reports what a reader did with an article, from the page that just rendered it:POST /v1/content-interactions with the same publishable
ek_live_… key the client already holds, so a page that already loads the SDK
adds one line. The article ids come from
the content API, and
Render a club’s articles has it in place
on a working page.
It takes no tags, and there is no option to add any: the server reads the
article’s tags off the article. If a caller could send them, your developers and
your marketers would be choosing tag strings for the same vocabulary, and
members’ tag affinity would split in two.
Leave userId out and the client identity is used instead, when that identity
is a platform user id (identify({ userId })). A member external id does not
work here, because this endpoint resolves readers by platform user id only.
Only signed-in readers can be reported, so an anonymous reader is invisible
to these counts rather than counted anonymously. Passing userId blank says
exactly that, which is why a blank one drops instead of falling back to the
identity: an empty string is what a template renders once the reader signs out,
and the read is not theirs.
Reports are not batched or queued: the endpoint takes one per request. A read
counts once per member, per article, per day, so a refresh does not inflate the
number and a redelivered report is deduped rather than double-counted.
A refusal arrives as a
202 naming a reason, not as an error, because each one
is a normal state of a page rather than a broken request:
content_untagged is the one worth wiring an alert for: an article published
with no tags reports zero reads forever, and tagging it fixes reads from that
point on.
Content interactions never earn XP, and no setting changes that. A read reported
by a browser cannot be trusted to pay out. See
the content API overview.
Flushing
Config reference
Tags
Tags are trimmed + lowercased client-side (matching server normalisation) and must fit^[a-z0-9][a-z0-9_.:-]{0,63}$ - max 10 per event. Invalid tags are
filtered out; an event whose tags are all invalid is dropped and reported.
The tracking key is publishable - it ships in client code like an
analytics measurement id. Abuse controls are the key’s per-origin allowlist
and per-key rate limiting
(issue keys via the API), not key secrecy.