Server-side tracking
Send events from your own servers: subscriptions, invoices, background jobs. Things a visitor can’t fake and an ad blocker can’t hide, in the same reports and on the same people as your browser events.
Updated
The server API is available on every project. This page covers the ideas and the keys; Send events is the full guide to the endpoint, and the canonical reference in the app lists every error code.
Why server-side
- Nothing to block. Ad blockers and privacy extensions can stop a script in a browser, not a request between two servers.
- Nothing to fake. A server key is secret and never works from a browser, so events sent with it only come from your code.
- No page needed. Renewals, webhooks, refunds, background jobs and exports happen with no browser open.
- The source of truth. Revenue and plan changes come from the system that knows, not from a thank-you page that might not load.
Keep the script tag for pageviews, clicks and scroll depth. The two work together: a funnel can start on your pricing page and end at “Invoice paid”.
How it fits
- A server source sits in your project next to your website and web app sources. Its events go into the same reports.
- People are joined by your user ID. Send the same ID from your server as
distinct_idthat the browser passes toidentify, and both sides land on one person. Identity from the server explains the rules. - Sessions stay honest. A server event isn’t part of a browser session unless you pass one, so it never inflates session counts.
- It counts like any event toward your monthly total. Identify calls are free.
Add a server source
- In the app, open Sources and add a source. Choose Server: “A secret key and the Node SDK or HTTP API.” (New projects can pick it on the welcome screen too.)
- Name it after the system that sends, such as “Billing backend”.
- Create its first key, and copy it somewhere safe. The source’s page then shows how to send an event.
Server keys
A server key (cks_live_…) belongs to one server source and can only send events to it. Server keys live on that source’s page, under Secret keys. (Settings › API access is for the other direction: read-only keys that let Claude, Codex or your scripts read your analytics.)
- Shown once. When you create a key you see the whole secret one time. Afterwards the app shows its first characters, its last four and when it was last used; only a hash is stored.
- Store it as a secret, for example in an environment variable called
CLICKCLACKS_SERVER_KEY, and send it asAuthorization: Bearer cks_live_…. - Two working keys per source, so you can roll without downtime. Roll key creates a new one and keeps the old one working for the time you choose: stop it now, keep it for 24 hours, or keep it for 7 days.
- Revoke takes effect on the next request.
- Never from a browser. A request carrying an
Origin,Sec-Fetch-SiteorSec-Fetch-Modeheader is refused with403 browser_not_allowed, and the API sends no CORS headers. The source’s health panel warns you if that happens and links to Roll key. - Older
sk_live_…keys keep working. Rolling one creates acks_live_key and leaves the old one working until you revoke it.
Send your first event
The same snippets the app shows. Each reads the key from CLICKCLACKS_SERVER_KEY.
// npm install @clickclacks/node
import { ClickClacks } from '@clickclacks/node'
const clickclacks = new ClickClacks({
key: process.env.CLICKCLACKS_SERVER_KEY,
})
clickclacks.track({
event: 'Subscription started',
distinctId: 'user_8412',
properties: { plan: 'pro' },
})
process.on('SIGTERM', () => clickclacks.shutdown()) A 202 response means it was accepted. Open Realtime or Events and filter to the server source to see it.
SDK or HTTP?
- Node.js, including serverless and Cloudflare Workers: use @clickclacks/node. It batches, compresses, retries and adds an
insert_idfor you. - Anything else: one
POSTwith JSON. Any language has curl, Python, Go and Ruby examples with retries.
Next steps
- Send events: items, batching, idempotency, limits and errors.
- Identity from the server: identify from your backend and join browser history.
- Limits: every size and rate in one table.