Skip to main content

Server-Side Event Tracking

Track events from your backend — order placed, subscription renewed, invoice paid — straight into Hober segmentation and journeys. These are the events that never happen in a browser or app, so they need a server key, not the client SDK key.

The HTTP API is the contract: everything below works with curl alone. Official server SDKs (Node.js, Python, Go, Java) wrap the same API and are coming next; nothing here requires them.

Which key goes where

Client SDK keyServer key
Looks likesk_…hober_srv_…
Secret?No — publishable, ships inside apps and web pagesYes — server-side only, shown once at creation
HeaderX-SDK-Key: sk_…Authorization: Bearer hober_srv_…
Batch limit50 events/request1000 events, 500 KB/request, 32 KB/event
occurred_at backdatingup to 48 hoursup to 90 days
external_id as actor
Revocable individually✗ (rotate the tenant secret)✓ (Settings → Server Keys)
The sk_ prefix is not a secret key

Despite reading like Stripe's "secret key", the sk_… client SDK key is publishable by design. The hober_srv_… key is the secret one — never embed it in a client application, and revoke it immediately if it leaks.

1. Create a server key

Dashboard → Settings → Server KeysCreate Server Key. The key is shown once — copy it into your secret manager. Keys carry the events:write scope and can be revoked individually at any time.

2. Track an event

curl -X POST https://api.hober.io/v1/events \
-H "Authorization: Bearer $HOBER_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_name": "order.placed",
"external_id": "user-483",
"idempotency_key": "0197a3b2-7c1e-7f7e-9d7c-1b2a3c4d5e6f",
"properties": { "order_id": "ord_921", "total_cents": 12900 }
}'
{ "accepted": 1 }

external_id is your user identifier — the same value you registered the subscriber with. No need to look up Hober's subscriber UUID first. An unknown external_id fails the request with 422 so integration bugs surface immediately instead of silently dropping events.

Batches use the events envelope:

curl -X POST https://api.hober.io/v1/events \
-H "Authorization: Bearer $HOBER_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "event_name": "subscription.renewed", "external_id": "user-483", "properties": { "plan": "growth" } },
{ "event_name": "invoice.paid", "external_id": "user-517", "properties": { "amount_cents": 4900 } }
]
}'

3. Make retries safe

Ingestion is idempotent per idempotency_key (unique within your tenant). Supply your own — an outbox row id, a UUIDv7 per event — and retry the whole batch on any network error or 5xx: already-stored events are skipped, new ones are ingested, and nothing is double-counted.

Backdating

occurred_at defaults to receipt time and accepts RFC3339 timestamps up to 90 days in the past on the server tier (48 hours on the client tier). Anything older is rejected with 422 — bulk historical imports are a separate surface, not the live endpoint.

Trust tiers in your data

Every event records the tier it arrived through as source: client | server, stamped from the credential — a source field in the payload is ignored. Segments and analytics can rely on server-sourced events not being forgeable by anything shipped inside your app.

Revoking a key

Settings → Server Keys → Revoke. Requests with a revoked key start failing with 401 within about a minute (validation results are briefly cached on the ingest path).

Reference