Skip to main content

Event Catalog

Domain events are versioned (type.vN), CloudEvents-shaped, written via the transactional outbox and relayed to NATS JetStream by cmd/relay. Consumers must be idempotent.

cmd/relay polls outbox_event for unpublished rows (FOR UPDATE SKIP LOCKED, safe to run multiple replicas), publishes the full envelope to NATS JetStream on subject events.<type>, and marks a row published only on a successful publish. Delivery is at-least-once: a crash between a successful publish and marking the row republishes it on the next poll. Live-verified 2026-07-28 end to end (real HTTP request → outbox row → relay → real JetStream delivery, confirmed by a durable consumer).

A real consumer now exists: cmd/notifier runs a durable JetStream consumer whose subject filter is built from every registered event type, and turns messages into notification rows with retry, exponential backoff and a dead_letter terminal state. It is deployed as its own service.

The event surface is described by 45 AsyncAPI documents under contracts/asyncapi. They are generated from the Go source that emits the events — cmd/asyncapi-gen, whose own package doc puts the total at roughly 370 event types — and asyncapi-gen -check runs in apps/api/Dockerfile, making this the one contract check that genuinely executes on every build. The event list is authoritative (it comes from the EventXxx constants); the payload is best-effort, and the generator reports the roughly two fifths it cannot resolve rather than guessing. The catalogue below covers three producers as an introduction, not as an inventory.

See ADR-006 for what's still open.

Envelope​

{
"id": "uuid",
"type": "animal.created.v1",
"source": "animals",
"time": "UTC timestamp",
"correlationId": "uuid",
"causationId": "uuid",
"tenantId": "uuid",
"countryCode": "FR",
"data": {}
}

Catalog (implemented — producer: animals)​

TypeWhenKey payload
animal.created.v1animal createdanimalId, ownerId, species
animal.updated.v1identity updatedanimalId
animal.delegate_granted.v1delegation grantedanimalId, userId, scope, expiresAt
animal.delegate_revoked.v1delegation revokedanimalId
animal.emergency_updated.v1emergency profile changedanimalId
animal.photo_added.v1photo metadata addedanimalId

Ordering: per-aggregate via causationId correlation (best-effort; JetStream does not guarantee cross-subject ordering). Retry: unpublished rows are retried on every relay poll (1s interval) forever — cmd/relay sets no maximum and there is no dead-letter path on the publish side, so an event that cannot be published retries indefinitely (tracked in ROADMAP.md as EVENT-004). Retry, backoff, dead-lettering, inspection and replay are real, and are confined to the notifications consumer; they are not properties of the relay.

Catalog (implemented — producer: safety)​

TypeWhenKey payload
animal.lost_reported.v1lost report createdreportId, species, kind, coarsened lat/lng, lastSeenAt
animal.sighting_reported.v1sighting submittedsightingId, reportId
animal.found_reported.v1found report createdfoundId
animal.recovered.v1lost report resolvedreportId, status

Privacy: safety events never carry a precise location — only the coarsened (~2 km grid) point, matching the public alert projection.

Catalog (implemented — producer: organizations)​

TypeWhenKey payload
organization.created.v1organization createdorgId, name, kind, ownerId
organization.member_invited.v1invitation sentorgId, email, role
organization.member_joined.v1invitation acceptedorgId, userId, role
organization.member_removed.v1member removedorgId, userId

The rest​

The other AsyncAPI documents cover the remaining producers — payments, calendar, telehealth, marketplace, academy, geofencing, prescriptions, connected devices and the rest. Read contracts/asyncapi/<module>.yaml for any of them; it is generated from and checked against the Go source, so it cannot drift the way this page did.