Track what happens on your servers.
Subscriptions, invoices, exports: the moments that matter often happen where no browser is open. Send them with one HTTP call and they join the same people and reports as everything your site tracks.
An illustration. The endpoint, fields and responses are real; the data is an example.
- items per request, up to 1 MiB, gzip welcome
- 500
- events per project, with bursts to 5,000/s
- 1,000/s
- of late events kept at the time you send
- 7 days
- surcharge for the API, on any plan
- 0
Why server-side
The events you most need to trust, counted where they happen.
Keep the script tag for pageviews and clicks. Send the rest from your backend, and read both in the same funnels, retention and people.
No ad blockers in the way
An extension can stop a script in a browser, not a call between two servers. A blocked tracker no longer means a missing purchase.
Can’t be faked by a visitor
Server keys are secret and refused from any browser, so what you send with one only ever comes from your code, never from someone’s devtools.
Backend and revenue events
Invoices, renewals, exports and webhooks happen with no browser open. Send them with $revenue and $currency, checked and kept with the event.
Retries that never double up
Give every item an insert_id and retry as often as you need. Repeats within a Source are stored once.
Generous limits, every plan
1,000 events a second per project, bursts to 5,000, and 100 requests a second per key. No API tier to buy.
How it works
A key, a batch, then the same reports.
One endpoint takes batches of track and identify items. There’s no schema to register first: send an event and it’s in your reports.
Create a server key
In onboarding, on a server Source, or in Settings › API keys. Keep it in an environment variable.
Send a batch
Up to 500 items in one request, from any language that can make an HTTPS call.
Read it with everything else
Your distinct_id joins the person who identified in the browser with the same id, so one history covers both.
Responses and keys
Every item accounted for, and a key that stays on your server.
Partial success, spelled out
Valid items are kept. Each refused one comes back in errors[] with its index, a stable code and the field at fault.
A dry run before you ship
Add ?validate=true to see every item exactly as it would be stored, with nothing stored or counted. ?strict=true refuses the whole batch on one bad item.
Clear about slowing down
Over a limit you get 429 with Retry-After, and nothing in that request was stored, so resending is always safe.
Server keys, cks_live_…
- Shown once, then only its prefix and last four characters.
- Up to two active per Source, so you can roll one now, in 24 hours or in 7 days.
- Revoking takes effect on the next request.
- Never works from a browser: no CORS, and a browser request is refused.
POST /api/v1/batch · 500 items
HTTP/1.1 202 Accepted
{
"accepted": 498,
"dropped": [
{ "index": 12, "reason": "bot_filtered" }
],
"errors": [
{ "index": 3, "code": "timestamp_too_old",
"field": "timestamp",
"message": "Timestamps older than 7 days …" }
],
"request_id": "8c1f2d…-FRA"
}One old timestamp didn’t sink the batch: 498 kept, one refused with a reason, one bot filtered by the Source’s policy.
Limits
The same on every plan.
Server events count like any other event toward your monthly total. Identify calls are free. Over a rate limit, the API answers 429 with Retry-After and stores nothing from that request.
- Items per request
- 500
- Body
- 1 MiB uncompressed; gzip accepted
- Properties per item
- 8 KiB, 255 keys, depth 5
- Events per project, sustained
- 1,000 per second
- Events per project, burst
- 5,000 per second
- Requests per key
- 100 per second
- Timestamps accepted as sent
- 7 days back to 10 minutes ahead
Rate limits are counted per Cloudflare location, so a sender in many regions at once may briefly go over them. Limits in the docs
Setup
One request, in the language you already use.
Every example reads the key from CLICKCLACKS_SERVER_KEY, so it’s safe to paste into your code. Send an insert_id with each item and reuse it when you retry.
Endpoint · JSON, up to 500 items
POST https://app.clickclacks.io/api/v1/batchAuthorization · Bearer cks_live_…
curl https://app.clickclacks.io/api/v1/batch \
-H "Authorization: Bearer $CLICKCLACKS_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[{"event":"Invoice paid","distinct_id":"user_8412","insert_id":"inv_2291","properties":{"amount_cents":4900}}]}'FAQ
The server API, in detail.
The server API is in early access: it’s live, and we’re turning it on project by project. Request early access and mention server-side tracking, and we’ll let you know when it’s on for your project.
Anything that can make an HTTPS request. The docs have curl, Python and Go examples with safe retries. A Node SDK, @clickclacks/node, is coming to npm; it batches, gzips and retries for you.
Server events count like any other event toward your monthly total. Identify calls are free. The server API is on every plan, with the same limits.
Send your own user id as distinct_id. If that person has already identified in the browser with the same id, the event joins their history. To tie an event to one particular browser, send its anonymous_id as well.
No. A server key is a secret, so the API refuses any request that comes from a browser and sends no CORS headers. For browsers, use the ClickClacks script tag and its public key.
Timestamps are trusted from 7 days in the past to 10 minutes ahead. Older items are refused with timestamp_too_old; an endpoint for backfilling history is coming.
Start hearing your clicks.
One script tag. Your first report in minutes.
4.3 KB · pageviews, clicks and scroll depth, automatically