A message was sent or received, including attachments. There is no separate attachment event: a file arrives as a message with a non-text type and the file in attachments.
The envelope is identical on every channel. What differs is inside data: Instagram carries a source and an Instagram sender, Telegram carries a Telegram user id and no source, and an AI or operator reply carries sender: null.
data.external_id is your idempotency key. Attachment URLs are time-limited, so download the file when the event arrives rather than storing the URL.
Semver for the payload shape. Fields are added without a major bump; nothing is removed or repurposed without one.
Return any 2xx as soon as you have stored the event. Anything else, or a timeout, is retried: 6 attempts over about an hour, then dead-lettered.