Owlsignal

Docs

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.

Using an AI assistant? Point it at owlsignal.dev/llms.txt — the whole integration brief (wire format, naming conventions, server-computed signals, SDK APIs, privacy rules) as one plain-text fetch, generated from the same source this page describes.

Quickstart

Three steps:

  1. Sign up — free, no card. Your first app + API key are generated by the onboarding wizard.
  2. Drop the SDK (or a curl) into your build, point it at the app's key, and ship a single event.
  3. Watch it appear in the dashboard within seconds. From there, define a loop or wait for users.

One-shot from a terminal

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"
  }]}'

Wire format

POST /api/v1/events with a JSON body containing an events array (1–100 events per batch). Every event has:

FieldTypeNotes
build_versionstring1–64 chars. Free-form (e.g. 1.4.0).
session_iduuidStable per session. Same id ties events to one session row.
player_hashstring32–64 chars. Anonymous device hash. No PII.
tsISO 8601Client clock. Server stamps separately too.
typeenumOne of progression, design, resource, error, session.
namestring1–100 chars. Convention: Verb:Subject e.g. Start:Run, CTA:Clicked.
valuenumber?Optional numeric (duration, score, currency delta).
dim01..dim03string?Optional dimensions (≤64 chars). Shown in Live and session views, and countable as distinct values in a funnel stage ("built 5 different units").
dataobject?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.

Event types

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.

session
Session:Start / Session:End. Used to compute session duration. The SDK auto-fires these — you almost never call them by hand.
progression
Phase markers: Start:Run, Complete:Tutorial, Fail:Boss. These are what funnel stages match on.
design
Gameplay or UX signals that aren't pure phase markers: FirstTime:Combo, CTA:Clicked, Friction:DeadEnd. Default for "interesting moment, not sure what to call it."
resource
Soft-currency tracking. Convention: Source:Gold with positive value, Sink:Gold with positive value (the verb encodes direction).
error
Exceptions / critical issues. Powers the Errors page. Optional data.message, data.stack, data.scene, data.signature — signature groups identical errors.

Web SDK (JS/TS)

Install from npm, then initialize once near the root of your client bundle:

npm install @owlsignal/web
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', 50);            // value is POSITIONAL
OwlsignalClient.TrackError('Error:Web:CheckoutFailed', { data: { stage: 'payment' } });

// 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);

Auto-events

Once initialized, the SDK fires these for you — never instrument them yourself:

  • Session:Start on init.
  • Session:End on pagehide (sent via fetch keepalive).
  • Error:Web:Unhandled on a global error, and Error:Web:UnhandledRejection on an unhandled promise rejection.

The web SDK deliberately does not fire Client:Foregrounded / Client:Backgrounded — browser tab focus fires constantly (alt-tab, dev tools) and drowns out the real signal. Those are Unity-only. Turn the rest off with autoTrackLifecycle: false / autoTrackErrors: false.

Events are batched (every 30s or 30 events, whichever first). 5xx responses retry with exponential backoff; 4xx drops the batch.

Stateless mode (no consent banner)

By default the SDK keeps its anonymous hash in localStorage under owlsignal:player_hash, so a returning visitor is recognised across sessions. Pass persistPlayerHash: false and it writes nothing to the device — the hash lives in memory for that page load only.

OwlsignalClient.Initialize({
  apiKey: 'pk_live_...',
  persistPlayerHash: false   // store nothing on the visitor's device
});

Why you'd want it: the EU ePrivacy Directive (Art. 5(3)) governs storing or accessing information on a user's device. It isn't a rule about cookies specifically, and it isn't a rule about personal data — so "it's only local storage" and "it's anonymous" are both the wrong test, and an analytics ID in localStorage is in scope on the same terms a cookie would be. Writing nothing is what actually takes you out of scope. That's how we run owlsignal.dev itself, and why this site has no consent banner.

What it costs: every page load looks like a new visitor, so cross-session retention goes away — D1/D7 cohorts, SessionCount:N and ActiveDays:N stop being meaningful. Anything inside one session — funnels, drop-off, errors — is unaffected. The opt-out flag still uses localStorage either way: storing a choice someone made in order to honour it is the textbook strictly-necessary case.

Not legal advice — your DPO's call. Defaults are chosen for measurement; this switch is there when your compliance posture asks for it.

Unity SDK

Install via the Package Manager: Window → Package Manager → + → Install package from git URL… and paste the URL below (append #v0.1.0-alpha.1 to pin a release — what you want for a shipping build). Shape-equivalent to the web SDK, with C# named arguments:

https://github.com/zisomediadev/owlsignal-unity.git
using Owlsignal;

OwlsignalClient.Initialize(apiKey, endpointUrl, buildVersion);

OwlsignalClient.TrackProgression("Complete:Tutorial");
OwlsignalClient.TrackDesign("FirstTime:Action");
OwlsignalClient.TrackResource("Purchase:Made", value: 19.0);   // value is a NAMED arg
OwlsignalClient.TrackError("Error:Unity:SaveCorrupt");

OwlsignalClient.Flush();   // force-send; also called automatically on quit

Auto-events

  • Session:Start on init, Session:End on quit.
  • Client:Foregrounded / Client:Backgrounded on focus change (Unity only).
  • Error:Unity:Unhandled on an unhandled C# exception — carries message, stack_top, scene.

Godot SDK

Grab the latest zip from owlsignal-godot releases, copy its addons/owlsignal/ into your project's addons/, then enable it (Project → Project Settings → Plugins) — that registers OwlsignalClient as an autoload, callable from any script.

OwlsignalClient.initialize({
    "api_key": "pk_live_...",
    "build_version": "1.4.0",
})

OwlsignalClient.track_progression("Complete:Tutorial")
OwlsignalClient.track_design("CTA:Clicked", { "dim01": "main-menu-play" })
OwlsignalClient.track_resource("Source:Coins", { "value": 50.0 })   # value in the opts Dictionary
OwlsignalClient.track_error("Error:Godot:SaveCorrupt")

OwlsignalClient.flush()

Auto-events

  • Session:Start on init, Session:End on window close.
  • Client:Foregrounded / Client:Backgrounded on focus change (backgrounding also flushes).
  • Current scene name attached to every event as screen.

No automatic crash event. GDScript exposes no global hook for unhandled script errors, so unlike Unity/Unreal there is no Error:Godot:Unhandled — report failures you care about yourself with track_error("Error:Godot:…").

Unreal SDK

Clone it straight into your project's Plugins/ folder (or download a zip from releases), let the editor compile it, enable it, then configure once from your GameInstance:

git clone --depth 1 https://github.com/zisomediadev/owlsignal-unreal.git Plugins/Owlsignal
#include "OwlsignalSubsystem.h"

UOwlsignalSubsystem* Owlsignal = GetGameInstance()->GetSubsystem<UOwlsignalSubsystem>();

FOwlsignalInitOptions Options;
Options.ApiKey = TEXT("pk_live_...");
Options.BuildVersion = TEXT("1.4.0");
Owlsignal->Configure(Options);

Owlsignal->TrackProgression(TEXT("Complete:Tutorial"), FOwlsignalTrackOptions());
Owlsignal->TrackResource(TEXT("Purchase:Made"), FOwlsignalTrackOptions::WithValue(19.0));
Owlsignal->TrackError(TEXT("Error:Unreal:SaveCorrupt"), FOwlsignalTrackOptions());

Auto-events

  • Session:Start on configure, Session:End on GameInstance shutdown.
  • Client:Foregrounded / Client:Backgrounded via FCoreDelegates.
  • Error:Unreal:Unhandled — the SDK watches GLog for Error/Fatal (where failed checks and ensures surface), rate-limited to one per second.
  • Current level name attached as screen (PIE prefix stripped).

Server-computed signals

These look like event names but are derived server-side from a player's own timeline. You never fire them — use them as loop stage names and Owlsignal works them out from the events you already send.

Day:N:Returned
Came back on exactly day N after first play (classic N-day retention). Not the same as "within N days".
Day:N:ReturnedWithin
Came back on any day 1…N — use this for "did they return within a week?".
SessionCount:N
Reached N sessions.
SessionDuration:N:minutes / :hours
Total playtime ≥ N.
ActiveDays:N
Active on ≥ N distinct days.

There is no generic Error:Unhandled event — crashes arrive under the real per-platform names above (Error:Unity:Unhandled, Error:Web:Unhandled). A stage can list several event names; they're OR-matched.

Authentication

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.

Rate limits & tier behavior

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.

Privacy & opt-out

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.

Anonymous is not the same as storing nothing. By default the web SDK keeps its hash in localStorage, which is storage on your visitor's device and therefore yours to disclose — being anonymous doesn't exempt it. If that's a problem for your compliance posture, stateless mode writes nothing at all.

Data residency: Postgres in eu-central-1 (Frankfurt), functions in fra1 (Frankfurt). See privacy for the full posture.