Every event shares a common envelope, then a per-type payload:
{
"event_id": "uuid-v4",
"ts": "2026-06-18T10:00:00Z",
"platform": "roblox | steam | unity | fortnite",
"game_id": "920587237",
"player_id": "hashed-player-id or null",
"session_id": "uuid-v4",
"type": "session_start | purchase | ...",
"payload": {}
}
event_id is deduped within 24h, so retries on network failure are safe.
The 13 built-in types
| Type | Auto-emitted? | Required payload | Rolls up into |
|---|
session_start | Yes (join) | — | DAU/WAU/MAU, geo |
session_end | Yes (leave) | — | session length, playtime, retention |
purchase | Manual / gamepass hook | sku, price | ARPDAU, revenue timeline, top products |
performance | Yes (Roblox, per session) | fps_avg | FPS histogram, p5/p50/p95 |
crash | Yes (Roblox LogService) | stack_trace | crash groups, top crashes |
error | Yes (Roblox LogService) | severity, message | error feed |
ad_impression | Manual | format, network, revenue_usd | ARPDAU-ads, eCPM, ad revenue |
brand_zone_enter | Manual | zone_id | brand-zone reach |
brand_zone_exit | Manual | zone_id | brand-zone dwell time |
equip | Manual | item id | repeat-equip / brand-asset usage |
share | Manual | — | share counts |
level | Manual | level | stored + searchable (no rollup) |
custom | Manual | name | stored + searchable (no rollup) |
level, custom, and any unrecognized type are stored verbatim and are
searchable in the Event Explorer, but are not aggregated into a dashboard.
Examples
{ "type": "session_start",
"payload": { "country": "US", "membership": "Premium" } }
For complete per-type field tables and validation rules, see the dedicated
SDK docs → Event types reference.
Validation rules
| Rule | Behaviour |
|---|
purchase missing sku or price | 422 Unprocessable Entity |
| Payload with PII-shaped key/value (any depth) | 422 PII guard |
Duplicate event_id within 24h | Silently deduped, 202 |
(platform, game_id) not linked to your team | 403 |
| Batch size > 500 events | 413 |
| Monthly quota exceeded | 429 with Retry-After |