Send events
What to send, when to send it, and how Hatched turns events into effects.
Events are the only way the outside world changes a buddy. Everything the
rule engine does starts with a POST /events.
Shape
await hatched.events.send({
eventId: 'evt_01HXYZ', // for idempotency
userId: 'user_42',
type: 'lesson_completed',
properties: {
lessonId: 'lesson_17',
durationMs: 5 * 60 * 1000,
score: 0.92,
},
occurredAt: '2026-04-22T10:30:00Z', // optional; defaults to now
});The SDK serialises camelCase field names to snake_case on the wire
(userId → user_id, occurredAt → occurred_at). You don't have to
think about it — just pass camelCase in.
Pick stable event types
Event types are the string keys rules match against. Choose them once and don't rename them — existing coin rules, badge conditions, and analytics queries reference them. Use snake_case, present-tense verbs:
lesson_completed
lesson_started
daily_login
checkout_completed
quiz_passed
task_assignedProperties are yours
The rule engine doesn't require a fixed property shape — you define it.
Whatever you send becomes queryable via custom conditions and visible in
the event log. Stay consistent: if durationMs exists for
lesson_completed, always include it.
Idempotency
Pass a stable eventId and you're safe to retry:
await hatched.events.send({
eventId: `lesson_${lessonId}_${userId}`,
userId,
type: 'lesson_completed',
properties,
});Hatched stores eventIds per customer and returns the cached effect on
duplicate submissions. Without eventId, retries can produce duplicate
effects.
Order doesn't matter (usually)
Events for the same buddy serialise on a row lock. You can send them in parallel — the rule engine will process them one at a time. You don't need a queue on your side unless you want ordering guarantees across different users.
Batch mode
For bulk imports, send up to 500 events in a single request:
await hatched.events.sendBatch([
{ eventId: 'e1', userId, type: 'lesson_completed', properties: { ... } },
{ eventId: 'e2', userId, type: 'quiz_passed', properties: { ... } },
]);Each entry is processed independently — partial failures are reported per event without rolling the batch back.
Return shape
send resolves with the effects the rule engine applied:
const effects = await hatched.events.send({ ... });
console.log(effects);
// {
// coins: 10,
// badgesAwarded: ['first_lesson'],
// badgesReady: [],
// tokens: [],
// evolutionReady: false,
// streakMilestones: [],
// }Use effects.badgesAwarded or effects.evolutionReady to trigger
celebratory UI on your side the same tick as the event fires.