HatchedDocs
Guides

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 (userIduser_id, occurredAtoccurred_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_assigned

Properties 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.