Events
An Event is the atomic fact your application reports to QuestKit. Every gamification rule, balance change, and mission progression eventually traces back to an event. Events are intentionally narrow:
interface Event {
userId: string;
name: string; // e.g. "purchase.completed"
payload: Record<string, unknown>; // arbitrary structured data
timestamp: number; // unix ms
idempotencyKey?: string;
}
Lifecycle
client ──POST /v1/events──► api worker
│
├─► KV idempotency (24h)
│
├─► D1 insert (events table)
│
├─► rule engine evaluates open missions
│ │
│ └─► mission_progress upsert + SSE broadcast
│
└─► Analytics Engine write
Idempotency
Pass Idempotency-Key as a request header (preferred) or idempotencyKey in the body. QuestKit caches the result for 24 hours per (userId, key) tuple. Replays return the original response with X-Idempotent-Replay: hit so retry loops are safe.
Where events come from
- Direct SDK calls (
useEvent().fireEvent(...),window.QuestKit.fireEvent(...)) - Direct REST calls from your backend (
POST /v1/events) - Inbound webhooks (Stripe etc.) via the webhook pipeline, which normalises provider payloads into the
Eventshape and queues them for async ingestion
Tips
- Use stable
namevalues — e.g.purchase.completed, notpurchase_2024_q4. Names are the join key for mission criteria. - Put filterable values in
payload—payload.productlets a mission filter to "buys ofproduct = "boots". - Send
timestampfrom the client when it matters (offline-first apps); QuestKit trusts the timestamp it receives.