A message schema is an API and needs versioning

A published message has consumers you do not control, and its shape is a contract whether or not anybody wrote one down.

{
  "specversion": "1.0",
  "type": "com.example.orders.placed.v1",
  "source": "/orders",
  "id": "9c1f4a7e-...",
  "time": "2021-06-14T09:41:02Z",
  "data": { "order_id": 8814, "total_cents": 4900 }
}

// the version in the TYPE. adding a field is safe;
// removing or renaming one publishes .v2 alongside .v1.

Publishing both versions during a transition is the only migration that does not require coordinating a release across teams, and it means the producer carries the cost rather than every consumer. CloudEvents is a reasonable envelope to adopt rather than invent, and the envelope is where the interesting fields live — id for deduplication, time for ordering, source for provenance. Consumers must ignore unknown fields, and that has to be stated rather than assumed.