http clients and apis
Event And Message Standards: CloudEvents
Event-driven systems need messages that can be routed, logged, traced, validated, and understood by more than one service. Standards such as CloudEvents provide a consistent envelope around event data so every team does not invent a different shape.
CloudEvents is often seen around webhooks, queues, event buses, serverless platforms, and integration systems. A PHP app may create CloudEvents, receive them over HTTP, or consume them from a queue.
The envelope and the data
A CloudEvent separates metadata from the business payload. The metadata explains what the event is. The data field contains the domain-specific information.
<?php
declare(strict_types=1);
$event = [
'specversion' => '1.0',
'id' => 'evt_123',
'source' => '/billing',
'type' => 'com.example.invoice.paid',
'time' => '2026-05-28T12:00:00Z',
'datacontenttype' => 'application/json',
'data' => [
'invoice_id' => 'inv_456',
'amount' => 1999,
'currency' => 'GBP',
],
];
echo json_encode($event, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL;
// Prints:
// "specversion": "1.0"
// "type": "com.example.invoice.paid"
// "invoice_id": "inv_456"
The envelope helps infrastructure and generic event consumers handle the event before they understand the specific business data.
Required fields
For a structured JSON CloudEvent, the important fields to recognise are:
specversion: the CloudEvents specification version, commonly1.0.id: a unique event ID from the producer.source: where the event came from.type: what happened.data: the event payload.
Other common fields include time, subject, datacontenttype, and extension attributes.
<?php
declare(strict_types=1);
function validateCloudEvent(array $event): array
{
foreach (['specversion', 'id', 'source', 'type'] as $field) {
if (!isset($event[$field]) || trim((string) $event[$field]) === '') {
return ['valid' => false, 'message' => $field . ' is required'];
}
}
if ($event['specversion'] !== '1.0') {
return ['valid' => false, 'message' => 'Unsupported CloudEvents specversion'];
}
return ['valid' => true, 'message' => 'CloudEvent envelope is valid'];
}
print_r(validateCloudEvent(['specversion' => '1.0', 'id' => 'evt_1', 'source' => '/billing', 'type' => 'invoice.paid']));
print_r(validateCloudEvent(['id' => 'evt_1']));
// Prints:
// [valid] => 1
// [message] => specversion is required
Envelope validation is not the same as business validation. An event can be a valid CloudEvent while still having invalid data for your application.
Type names
Event type names should be stable and specific. Names such as updated or event are too vague. Names such as com.example.invoice.paid or uk.co.example.user.email_changed are easier to route and search.
Good event types describe facts that already happened. Prefer invoice.paid over pay_invoice, because consumers are reacting to an event, not being commanded to perform an action.
HTTP binding
CloudEvents can be sent as structured JSON, where the whole event is the JSON body, or in binary mode, where metadata appears in HTTP headers and the body contains only the data.
Structured mode is easy to inspect:
Content-Type: application/cloudevents+json
Binary mode commonly uses headers such as:
ce-specversion: 1.0
ce-id: evt_123
ce-source: /billing
ce-type: com.example.invoice.paid
Content-Type: application/json
As a junior developer, the important part is to recognise that metadata may be in the body or in headers depending on the binding.
Idempotency and ordering
CloudEvents gives an event an id, but it does not magically make processing idempotent. Your consumer still needs to store processed IDs or otherwise make repeated events safe.
CloudEvents also does not guarantee ordering by itself. If event order matters, the surrounding transport, partitioning, database design, or consumer logic must handle it.
What to check
Before moving on, make sure you can:
- Separate event metadata from business data.
- Recognise the core CloudEvents fields.
- Validate an event envelope before processing
data. - Choose clear event type names.
- Explain the difference between structured and binary HTTP bindings.
- Avoid assuming that event IDs solve ordering or idempotency alone.
Practice
Practice: Validate A CloudEvent
Write a PHP function that validates a structured JSON CloudEvent envelope.
Requirements
- Decode JSON with exceptions enabled.
- Require
specversion,id,source, andtype. - Support only
specversionvalue1.0. - Require
datafor this exercise. - Return a clear result for valid event, invalid JSON, missing field, and unsupported version.
Show solution
This solution validates the CloudEvents envelope before any business-specific processing happens.
<?php
declare(strict_types=1);
function validateStructuredCloudEvent(string $rawJson): array
{
try {
$event = json_decode($rawJson, true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException) {
return ['valid' => false, 'message' => 'Event body must be valid JSON.'];
}
if (!is_array($event)) {
return ['valid' => false, 'message' => 'Event body must decode to an object.'];
}
foreach (['specversion', 'id', 'source', 'type', 'data'] as $field) {
if (!array_key_exists($field, $event) || $event[$field] === '') {
return ['valid' => false, 'message' => $field . ' is required.'];
}
}
if ($event['specversion'] !== '1.0') {
return ['valid' => false, 'message' => 'Only CloudEvents specversion 1.0 is supported.'];
}
return ['valid' => true, 'message' => 'CloudEvent envelope is valid.'];
}
$examples = [
'{"specversion":"1.0","id":"evt_1","source":"/billing","type":"com.example.invoice.paid","data":{"invoice_id":"inv_1"}}',
'{bad json',
'{"specversion":"1.0","id":"evt_1","type":"com.example.invoice.paid","data":{}}',
'{"specversion":"0.3","id":"evt_1","source":"/billing","type":"com.example.invoice.paid","data":{}}',
];
foreach ($examples as $example) {
$result = validateStructuredCloudEvent($example);
echo ($result['valid'] ? 'valid' : 'invalid') . ': ' . $result['message'] . PHP_EOL;
}
// Prints:
// valid: CloudEvent envelope is valid.
// invalid: Event body must be valid JSON.
// invalid: source is required.
// invalid: Only CloudEvents specversion 1.0 is supported.
After this check passes, the application still needs to validate the data payload for the specific event type.