The events Commune pushes to a consumer, rather than the resources a consumer pulls. Commune publishes state changes on 26 topics, each delivered as one HTTPS POST to an endpoint the consumer registered.
Every message shares one envelope, so a consumer can route on type and
dedupe on id without knowing anything about the specific event, and the
same values arrive as Commune-Event-Type and Commune-Event-Id headers
so both can be read before the body is parsed.
Delivery is at least once and unordered. A non-2xx response or a timeout
is retried with backoff, so a consumer has to treat id as the dedupe key
and tolerate replays. occurred_at is the ordering field, not arrival
time.
Two envelope fields say where a change came from rather than what
changed. actor names the credential when the change was made through
this API's write operations, and idempotency_key carries the key that
write was made under. Both are null for a change made anywhere else,
which is most of them.
If your consumer writes, read idempotency_key before you act. A
write through this API publishes an event, and that event is delivered to
every endpoint registered for the newsletter, including yours. A consumer
that reacts to events by writing therefore receives the echo of its own
write, cannot tell it from a change somebody else made, and writes again.
You already hold what breaks the loop: you generated the key you sent on
the write, so keep it and skip any event whose idempotency_key is one of
yours. actor is not the field for this. It names Commune's own id for
your credential, and no operation here tells you what that id is.
Registering an endpoint happens in the delivery portal, which
POST /newsletters/{newsletter}/portal-session mints a link into. The
endpoints already registered are readable at
GET /newsletters/{newsletter}/destinations.
What happened to a particular message is readable.
GET /newsletters/{newsletter}/delivery-attempts lists every handover
Commune made, filterable by the event_id a consumer reads off its own
Commune-Event-Id header, so "did that event reach me" is answerable from
both sides of the same identifier.