Send events from anywhere that can speak HTTPS. Read these in order if you're new; jump to a section if you came here looking for one specific thing.
Three steps:
curl -X POST https://owlsignal.dev/api/v1/events \
-H "Authorization: Bearer pk_PASTE_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"events":[{
"build_version":"0.1.0",
"session_id":"00000000-0000-0000-0000-000000000001",
"player_hash":"0123456789abcdef0123456789abcdef",
"ts":"2026-05-06T12:34:56Z",
"type":"session",
"name":"Session:Start"
}]}' POST /api/v1/events with a JSON body containing an events array (1–100 events per batch). Every event has:
| Field | Type | Notes |
|---|---|---|
build_version | string | 1–64 chars. Free-form (e.g. 1.4.0). |
session_id | uuid | Stable per session. Same id ties events to one session row. |
player_hash | string | 32–64 chars. Anonymous device hash. No PII. |
ts | ISO 8601 | Client clock. Server stamps separately too. |
type | enum | One of progression, design, resource, error, session. |
name | string | 1–100 chars. Convention: Verb:Subject e.g. Start:Run, CTA:Clicked. |
value | number? | Optional numeric (duration, score, currency delta). |
dim01..dim03 | string? | Optional indexed dimensions (≤64 chars). Used in dashboards for grouping. |
data | object? | Free-form key/value (string|number|boolean|null). Top-level only. |
Response is always 200 with { accepted, rejected, errors }. Bad events in a batch are
rejected but never fail the whole request — the SDK should never block the game.
Pick the one that best matches the moment. Type is purely a categorization signal — the dashboard groups things by it, but you can model nearly anything with design if unsure.
sessionSession:Start / Session:End. Used to compute session duration. The SDK auto-fires these — you almost never call them by hand.progressionStart:Run, Complete:Tutorial, Fail:Boss. Used by funnels and the Run analytics view.designFirstTime:Combo, CTA:Clicked, Friction:DeadEnd. Default for "interesting moment, not sure what to call it."resourceSource:Gold with positive value, Sink:Gold with positive value (the verb encodes direction).errordata.message, data.stack, data.scene, data.signature — signature groups identical errors.Lives in sdks/web in the repo. Copy the src/ contents into your project for now, or wait for the npm package release. Initialize once near the root of your client bundle:
import { OwlsignalClient } from '@owlsignal/web';
OwlsignalClient.Initialize({
apiKey: 'pk_live_...',
buildVersion: '1.4.0',
// endpointUrl is optional — defaults to https://owlsignal.dev/api/v1/events
});
// Then anywhere a meaningful moment happens:
OwlsignalClient.TrackProgression('Complete:Tutorial');
OwlsignalClient.TrackDesign('CTA:Clicked', { dim01: 'pricing-pro' });
OwlsignalClient.TrackResource('Source:Coins', { value: 50 });
OwlsignalClient.TrackError('NullRef', { data: { stack: 'at foo.bar:12' } });
// Optional: identify the user (still anonymous — this is just a stable hash).
OwlsignalClient.IdentifyUser('user-abc');
// Optional: respect a user opt-out flag.
OwlsignalClient.SetOptOut(true);
Once initialized, the SDK fires a few events automatically:
Session:Start on first track call after init.Session:End on pagehide (sent via fetch keepalive).Events are batched (every 30s or 30 events, whichever first). 5xx responses retry with exponential backoff; 4xx drops the batch.
Lives in sdks/unity in the repo, shape-equivalent to the web SDK (same OwlsignalClient.Initialize + Track* methods). The full quickstart and sample scene live in the SDK README — clone the repo or open the package directly to integrate.
Send the API key in the Authorization header:
Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Each key is scoped to one app inside one tenant. Generate keys in Settings → API keys. We store only a hash — the raw key is shown once when you generate it. Lost keys can be revoked + regenerated; we can't recover the raw value.
If your tenant has exceeded its monthly events budget, ingest silently
drops the batch and returns 200 with { accepted: 0, rejected: N, reason: "tier_limit" }.
The SDK never sees a 5xx for tier reasons; your game/app keeps working. The
dashboard surfaces the cap; we email at 80% and 100% of budget.
Server-side validation rejects bad events individually (the rest of the batch goes through). The response always lists which indexes were rejected and why.
Events carry an anonymous player_hash only — no email, no
IP, no real identity. Opt-out is a client-side contract: when the user toggles
"telemetry off", call OwlsignalClient.SetOptOut(true) (web) or the Unity equivalent. The SDK then drops events at the source — the server
can't detect opt-out because there's no identity to recognize.
Data residency: Postgres in eu-central-1 (Frankfurt),
functions in fra1 (Frankfurt). See privacy for the full posture.