# Send events

> What to send, when to send it, and how Hatched turns events into effects.

Source: https://docs.hatched.live/docs/guides/send-events

Events are the only way the outside world changes a buddy. Everything the
[rule engine](/docs/concepts/rule-engine) does starts with a `POST /events`.

## Shape

```ts
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_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:

```ts
await hatched.events.send({
  eventId: `lesson_${lessonId}_${userId}`,
  userId,
  type: 'lesson_completed',
  properties,
});
```

Hatched stores `eventId`s 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 100 events in a single request:

```ts
await hatched.events.sendBatch([
  { eventId: 'e1', userId, type: 'lesson_completed', properties: { ... } },
  { eventId: 'e2', userId, type: 'quiz_passed',      properties: { ... } },
]);
```

The API validates the whole batch before reserving quota or applying effects.
If any event type is not registered for its audience, the request fails and no
event quota is committed.

## Return shape

`send` resolves with the effects the rule engine applied:

```ts
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.
