Six event types leave our system for yours. A new integration usually subscribes to all six on day one and writes a handler for each, which reads as thoroughness and is mostly filing.
One question sorts them, asked once per event: if this delivery never arrives, does anything a traveler can see become wrong? Three of the six answer no. The other three answer yes, and they answer within minutes.
The three that can wait
These describe things that have already happened and will stay happened. They are worth having in your database eventually. They are not worth holding a launch for.
Miss a day of these and a nightly poll puts it right before anyone notices the gap. Nothing on a screen goes stale in the meantime, because nothing on a screen depends on them.
The three that change what a traveler sees
The other three carry state a traveler is actively waiting on. Drop one and your interface goes on displaying something you already know to be untrue, which is a worse place to be than displaying nothing at all.
Handle these three and your screens tell the truth. Handle the first three and your ledger agrees with ours. Both are worth doing; only one has a traveler attached to it.
What the handler has to do
Answer first, process afterwards. Write the payload down, return success, and do the real work off the request. A handler that calls three internal services before replying will time out under load, which makes us redeliver, which adds to the load.
Dedupe on the event id. Delivery is at least once, and a redelivery carries the same id as the original. Keep the ids you have already processed and drop repeats on sight. It is the same reasoning as idempotency keys on requests going the other way.
Verify the signature before your code branches. Every delivery is signed against the secret in your console. Anything that fails the check should stop there, whatever it says about itself.
Ignore types you do not recognise. We add events as coverage grows. If an unfamiliar type raises in your handler, our next addition becomes your next incident. A default branch that quietly does nothing keeps that from ever being your problem.
One more thing catches people out. Order is not guaranteed. Two status changes seconds apart can reach you the wrong way round, so compare the timestamp on the payload with what you last stored and let the later one win. Skip that and an approval can be overwritten by the notice that came before it.
Every delivery carries a unique event id and a signature header. Dedupe on the first, verify the second, and everything after that is your own logic. Both are documented, with the payload shapes, for developers.
A v1 worth shipping
One endpoint. Three meanings handled on it: a document state that moved, an action the traveler owes somebody, a refund that has landed. A table of processed event ids so repeats cost nothing. A signature check in front of all of it. A default branch for everything else.
Then a nightly job that polls the applications you have open and corrects whatever a deploy window swallowed. Belt and braces, at the price of one cron entry.
That is an afternoon. The other three events carry on arriving while you decide what you want to do with them, and their payloads will look the same in six months as they do today.