Message versioning by adding fields only

A message shape is a contract with every consumer that exists and every consumer that is mid-deploy.

{
  "type": "order.placed",
  "version": 2,
  "order_id": "01H8...",
  "total_cents": 4900,
  "currency": "GBP",
  "channel": "web"          // added in v2, optional
}

Adding an optional field is safe because an old consumer ignores it and a new one handles its absence. Everything else needs a new message type, published alongside the old one until every consumer has moved — which requires knowing who the consumers are. The version field is documentation rather than a mechanism; the compatibility comes from the additive rule, not from the number.