O contrato do outbox
Um evento é escrito na mesma transação das linhas que ele descreve. Essa única regra compra a garantia que importa: um evento nunca descreve uma escrita que sofreu rollback, e uma escrita que comitou nunca deixa de produzir seu evento. Publicar depois do commit tornaria os dois possíveis, e os dois são o tipo de bug que aparece como “o contador está errado às vezes”. Depois do commit, a API cutuca a fila para drenar o outbox. A cutucada é best-effort: ela compra latência. Uma varredura periódica é o que de fato garante a drenagem, mesmo quando toda cutucada se perde.O que é publicado hoje
Todo evento carrega a organização a que pertence; os do plano de clientes carregam
também o ambiente.
Os seis eventos de cobrança são o que o gateway diz, espelhado — nunca o que
alguém pediu a ele. Mudar de plano pela API é um pedido que o gateway aceita, e qual
é o plano depois disso chega com a resposta dele: os eventos acima são publicados
quando a cópia local de fato se move, seja porque um webhook chegou, seja porque uma
releitura noturna encontrou uma mensagem que nunca chegou. Então dá para construir
em cima deles, e eles não são um log de chamadas de API.
contact.identified dispara uma vez
Só na transição de verdade. Um segundo magic link é um login, não uma segunda
identificação — o SQL que marca o contato como identificado informa se mudou alguma
coisa, e o evento só é publicado quando mudou.
Payloads carregam ids
Entrega é ao menos uma vez, e fora de ordem
Duas propriedades que valem no barramento e valem igual no seu endpoint: a mesma entrega pode chegar duas vezes, e a ordem de chegada não é a ordem dos fatos. Cada envelope carrega uma sequência global atribuída no commit — é ela que ordena, não o relógio da chegada.Webhooks de saída
Eles são mais um consumidor dos eventos acima, em vez de um segundo caminho de publicação costurado por todo handler — que é justamente o motivo de o barramento vir primeiro, e por que nada da lista mudou de formato quando eles chegaram. O contrato de entrega viaja junto:id para deduplicar, sequence para ordenar.
Veja webhooks de saída.
Um fato é publicado aqui e deliberadamente nunca entregue para fora,
webhook.endpoint_disabled: ele seria endereçado ao endpoint que acabou de parar
de responder.