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 key | Server key | |
|---|---|---|
| Looks like | sk_… | hober_srv_… |
| Secret? | No — publishable, ships inside apps and web pages | Yes — server-side only, shown once at creation |
| Header | X-SDK-Key: sk_… | Authorization: Bearer hober_srv_… |
| Batch limit | 50 events/request | 1000 events, 500 KB/request, 32 KB/event |
occurred_at backdating | up to 48 hours | up to 90 days |
external_id as actor | ✗ | ✓ |
| Revocable individually | ✗ (rotate the tenant secret) | ✓ (Settings → Server Keys) |
sk_ prefix is not a secret keyDespite 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 Keys → Create 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
- Endpoint details: Events API
- Machine-readable contract:
docs/api/events-openapi.yaml