The outbox contract
An event is written in the same transaction as the rows it describes. That single rule buys the guarantee that matters: an event never describes a write that rolled back, and a write that committed never fails to produce its event. Publishing after the commit would make both possible, and both are the kind of bug that surfaces as “the counter is wrong sometimes”. After the commit, the API nudges the queue to drain the outbox. The nudge is best-effort: it buys latency. A periodic sweep is what actually guarantees the outbox drains, even when every nudge is lost.What is published today
Every event carries the organization it belongs to; customer-plane events carry the
environment too.
The six billing events are what the gateway says, mirrored — never what
somebody asked it to do. Changing a plan through the API is a request the gateway
accepts, and what the plan then is arrives with the gateway’s own answer: the events
above are published when the local copy actually moves, whether it moved because a
webhook arrived or because a nightly re-read found a message that never did. So they
are safe to build on and they are not a log of API calls.
contact.identified fires once
Only on the actual transition. A second magic link is a sign-in, not a second
identification — the SQL that marks the contact identified reports whether it
changed anything, and the event is published only when it did.
Payloads carry ids
Delivery is at-least-once, and unordered
Two properties that hold on the bus and hold just as much at your endpoint: the same delivery can arrive twice, and arrival order is not the order of the facts. Every envelope carries a global sequence assigned at commit — that is what orders them, not the clock on arrival.Outbound webhooks
They are one more consumer of the events above, rather than a second publishing path threaded through every handler — which is the whole reason the bus came first, and why nothing in the list changed shape when they arrived. The delivery contract travels with them:id to deduplicate, sequence to
order. See outbound webhooks.
One fact is published here and deliberately never delivered outward,
webhook.endpoint_disabled: it would be addressed to the endpoint that just
stopped answering.