Ingest a web analytics beat
Public collect endpoint used by the Notifuse Analytics browser SDK. Each beat carries the full cumulative session state (all pageviews and goals), so the server can rebuild the session from any single payload. Beats are typically sent as text/plain to avoid CORS preflights; the body is JSON regardless of Content-Type. Silently-dropped traffic (disabled feature, disallowed origin, bot user agents) still receives success responses.
The endpoint is public and unauthenticated, so the identity fields (contact_email + contact_email_hmac, or identify_token) are claims until they verify against the workspace secret. One that does not verify is not an error: the beat is recorded anonymously. A single malformed action is dropped the same way, leaving the rest of the beat intact.
Verifying the credential is the FIRST of three gates, not the only one. An address that clears all three is attached to the session; failing any of them costs the identity alone, silently — the beat is still stored and the response is still a 200 with no error. Success and every failure are therefore indistinguishable on the wire:
-
Signature (or token decryption) must verify against the workspace secret. The recipe is in the contact_email_hmac schema; getting the domain-separation prefix wrong is the usual cause.
-
Rate limits, applied to the IDENTIFIED path only (anonymous traffic is the normal firehose and stays unthrottled) and before the contact lookup, so an abusive caller cannot spend database reads: 120 identified beats per minute per workspace + address, and 600 per minute per client IP (that one counted across all workspaces, since an office shares one IP) — both sized around the SDK's 10-30s heartbeat. Exceeding either is deliberately never a 429, which the SDK would only queue for a retry that cannot fix it.
-
The address must ALREADY be a contact in the workspace. A signature proves who the caller is, never that the address belongs to anyone, so without this gate a workspace's own signing key could store the email of people who are not contacts, and erasure would be unenforceable — a deleted contact's next beat would re-stamp the address. The answer is cached for 60 seconds, so a freshly created contact can stay unidentified and a deleted one keep resolving for about that long.
This is why a correctly-signed beat for an address that is not a contact is indistinguishable from a bad signature: both return 200 and record the session anonymously.
Request body
Response
Beat accepted (or silently dropped)