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.