Event-Driven API Specification Template (AsyncAPI 3)
Async event contract: brokers, channels, operations, CloudEvents envelope, ordering, delivery, DLQ, schema evolution, AsyncAPI YAML
Cómo usar: Use when a service publishes events or messages other teams consume. Decide the delivery contract and schema compatibility rules before the first event ships, then document each message with a field table and a full example. Main pitfall: promising exactly-once or global ordering that the broker cannot give.
Vista previa
Event API Specification: [Event domain or application name]
Complete specification for an event-driven, asynchronous interface: brokers, channels, operations, messages and schemas, envelope, ordering, delivery guarantees, retries and dead-letter handling, schema evolution, security and consumer onboarding. The structure follows the concepts of the AsyncAPI Specification 3.0 (servers, channels, operations, messages, components) and uses the CloudEvents 1.0 envelope. Replace every
[bracketed]value and every row marked Example. Worked examples use an order-fulfilment event stream.
Document control
| Field | Value |
|---|---|
| Document ID | [EVT-SPEC-FULFIL-001] |
| Producing application | [Fulfilment service] |
| Event contract version | [1.2.0] |
| Document version | [0.1] |
| Status | [Draft / In review / Approved / Deprecated] |
| Owner (team) | [@team-fulfilment] |
| Classification | [Internal / Partner / Public] |
| Last updated | YYYY-MM-DD |
| Related | [AsyncAPI file path], [Schema registry subject], [REST API spec] |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Producer owner | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Platform / messaging team | [@name] | [Decision] | YYYY-MM-DD |
| Security | [@name] | [Decision] | YYYY-MM-DD |
| Consumer representative | [@name] | [Decision] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft |
| 0.2 | YYYY-MM-DD | [@name] | Example: Changed partition key from customerId to orderId |
| 1.0 | YYYY-MM-DD | [@name] | Example: Approved for production |
Contents
- Introduction
- Servers and brokers
- Channels
- Operations
- Messages and schemas
- Ordering and partitioning
- Delivery semantics
- Retries and dead-letter handling
- Schema evolution and compatibility
- Security
- AsyncAPI document
- Interaction flows
- Operations and service levels
- Consumer onboarding
- Glossary and references
1. Introduction
1.1 Purpose
[What the events communicate and why an asynchronous interface was chosen. Example: "The Fulfilment service publishes the lifecycle of each shipment so that billing, notifications and analytics can react without calling Fulfilment synchronously."]
1.2 Scope and context
| Item | Value |
|---|---|
| Bounded context | Example: Fulfilment |
| Producer(s) | Example: fulfilment-service (sole writer of every channel in this spec) |
| Known consumers | Example: billing-service, notification-service, analytics lake ingest |
| Interaction style | Example: Event notification with full state (event-carried state transfer) |
| Out of scope | Example: Commands to Fulfilment (use the REST API), inventory events |
1.3 Event design principles
- Events describe facts that already happened, named in the past tense.
- Each event type has exactly one producing application.
- Payloads carry enough state for typical consumers to act without a call-back; sensitive data is excluded unless listed in section 10.
- Consumers must tolerate duplicates, gaps in optional fields and unknown fields.
2. Servers and brokers
| Server ID | Environment | Host | Protocol | Version | Notes |
|---|---|---|---|---|---|
prod | Production | [broker-1.prod.example.com:9093] | Kafka | [3.7] | TLS and SASL/OAUTHBEARER |
staging | Staging | [broker-1.stg.example.com:9093] | Kafka | [3.7] | Masked data |
sandbox | Partner sandbox | [events.sandbox.example.com] | AMQP 0-9-1 / MQTT / WebSocket | [Version] | [Bridge for external partners] |
| [ID] | [Environment] | [Host] | [Protocol] | [Version] | [Notes] |
3. Channels
| Channel ID | Address | Messages | Partitions | Retention | Cleanup |
|---|---|---|---|---|---|
| CH-01 | fulfilment.shipment.v1 | ShipmentCreated, ShipmentDispatched, ShipmentDelivered, ShipmentFailed | 24 | 7 days | delete |
| CH-02 | fulfilment.shipment-state.v1 | ShipmentState (latest snapshot per shipment) | 24 | infinite | compact |
| CH-03 | fulfilment.shipment.v1.dlq | Any message from CH-01 that failed consumer processing | 6 | 30 days | delete |
| [CH-nn] | [Address] | [Messages] | [n] | [Retention] | [Policy] |
Channel naming convention: <domain>.<entity>[-<qualifier>].v<major>,
lower case, dot-separated. A new major version means a new channel.
4. Operations
| Operation ID | Action | Channel | Message(s) | Trigger | Expected rate |
|---|---|---|---|---|---|
| OP-01 | send | CH-01 | ShipmentCreated | Label purchased from carrier | Example: 40/s average, 300/s peak |
| OP-02 | send | CH-01 | ShipmentDispatched | Carrier scan at depot | 35/s average |
| OP-03 | send | CH-01 | ShipmentDelivered | Carrier delivery confirmation | 30/s average |
| OP-04 | send | CH-02 | ShipmentState | Every change on CH-01 | Same as CH-01 total |
| OP-05 | receive | orders.order.v1 | OrderPaid | Starts picking for a paid order | 45/s average |
| [OP-nn] | [send / receive] | [Channel] | [Message] | [Trigger] | [Rate] |
5. Messages and schemas
5.1 Envelope: CloudEvents attributes
All messages use the CloudEvents 1.0 envelope in binary content mode:
attributes travel as protocol headers (prefixed ce_ on Kafka) and the
payload is the message body. Examples in this document use the
structured JSON form for readability. partitionkey comes from the
CloudEvents partitioning extension.
| Attribute | Required | Value rule | Example |
|---|---|---|---|
specversion | Yes | Always 1.0 | 1.0 |
id | Yes | Unique per event from this source; UUID or ULID | 01HV7A0B2C9D8E7F6G5H4J3K2L |
source | Yes | URI reference of the producing application | /fulfilment-service/eu-west-1 |
type | Yes | Reverse-DNS event type with major version | com.example.fulfilment.shipment.dispatched.v1 |
time | Yes | RFC 3339 UTC time the fact occurred | 2026-03-15T14:02:11Z |
subject | Yes | ID of the entity the event is about | shp_01HV79ZZ0K |
datacontenttype | Yes | Payload media type | application/avro / application/json |
dataschema | Yes | URI of the schema version in the registry | https://schemas.example.com/fulfilment/shipment-dispatched/3 |
partitionkey | Yes | Ordering key (section 6) | ord_01HV6Z3K8Q |
traceparent | Yes | W3C Trace Context for distributed tracing | 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 |
| [extension] | [Yes / No] | [Rule] | [Example] |
5.2 Message catalogue
| Message | Type attribute | Schema subject | Format | Key fields |
|---|---|---|---|---|
| ShipmentCreated | com.example.fulfilment.shipment.created.v1 | fulfilment.shipment-created | Avro | shipmentId, orderId, carrier, serviceLevel |
| ShipmentDispatched | com.example.fulfilment.shipment.dispatched.v1 | fulfilment.shipment-dispatched | Avro | shipmentId, orderId, trackingNumber, dispatchedAt |
| ShipmentDelivered | com.example.fulfilment.shipment.delivered.v1 | fulfilment.shipment-delivered | Avro | shipmentId, orderId, deliveredAt, proofOfDelivery |
| ShipmentFailed | com.example.fulfilment.shipment.failed.v1 | fulfilment.shipment-failed | Avro | shipmentId, orderId, reasonCode |
| [Message] | [Type] | [Subject] | [Format] | [Fields] |
5.3 Example: ShipmentDispatched
Payload fields:
| Field | Type | Required | Description |
|---|---|---|---|
shipmentId | string | Yes | Shipment ID, prefix shp_ |
orderId | string | Yes | Order the shipment belongs to |
carrier | enum | Yes | DHL, UPS, LA_POSTE, OTHER; consumers must accept unknown values |
trackingNumber | string | Yes | Carrier tracking number |
dispatchedAt | timestamp | Yes | Time of the first carrier scan |
parcelCount | int | Yes | Number of parcels, at least 1 |
estimatedDeliveryDate | date | No | Carrier estimate, if supplied |
{
"specversion": "1.0",
"id": "01HV7A0B2C9D8E7F6G5H4J3K2L",
"source": "/fulfilment-service/eu-west-1",
"type": "com.example.fulfilment.shipment.dispatched.v1",
"time": "2026-03-15T14:02:11Z",
"subject": "shp_01HV79ZZ0K",
"datacontenttype": "application/json",
"dataschema": "https://schemas.example.com/fulfilment/shipment-dispatched/3",
"partitionkey": "ord_01HV6Z3K8Q",
"data": {
"shipmentId": "shp_01HV79ZZ0K",
"orderId": "ord_01HV6Z3K8Q",
"carrier": "LA_POSTE",
"trackingNumber": "6A12345678901",
"dispatchedAt": "2026-03-15T14:02:11Z",
"parcelCount": 1,
"estimatedDeliveryDate": "2026-03-17"
}
}
5.4 Example: [Message name]
[Repeat the field table and a complete example for every message in 5.2.]
6. Ordering and partitioning
| Topic | Rule |
|---|---|
| Partition key | Example: orderId, so all shipment events for one order land in one partition |
| Order guaranteed | Events for the same orderId are delivered in the order they were produced |
| Order not guaranteed | Across orders; across channels (CH-01 versus orders.order.v1) |
| Partition count | 24; changes only with a migration plan because key-to-partition mapping changes |
| Hot keys | One order rarely exceeds 10 events; no hot-key mitigation needed |
| Out-of-order handling | Consumers compare time and a per-entity sequence field and drop older updates |
The partition for a key is chosen by hashing:
so increasing the partition count remaps keys and can break ordering during the change.
7. Delivery semantics
7.1 Producer guarantees
- Delivery: at least once. Duplicates are possible after producer retries or failover.
- No silent loss: events are written to a transactional outbox table in
the same database transaction as the state change, and a relay publishes
them with broker acknowledgement from all in-sync replicas (
acks=all). - Producer idempotence is enabled to reduce, not eliminate, duplicates.
- Maximum publish delay from state change to broker: [5 s at p99].
7.2 Consumer obligations
| Obligation | How |
|---|---|
| Be idempotent | Store processed id values (or the entity version) and skip repeats for at least the retention period |
| Commit after processing | Commit offsets only after side effects are durable |
| Tolerate unknown fields and enum values | Use the reader schema; map unknown enums to a default branch |
| Handle out-of-order events | Apply only if the event is newer than the stored state |
| Bound processing time | Keep each message under [30 s] or move work to a job queue |
7.3 Exactly-once statements
[State whether any end-to-end exactly-once processing is offered. Example: "Not offered. Consumers achieve effectively-once results through idempotent processing as described in 7.2."]
8. Retries and dead-letter handling
| Failure type | Example | Consumer behaviour |
|---|---|---|
| Transient | Downstream timeout, database deadlock | Retry in place up to 3 times with backoff 1 s, 5 s, 25 s |
| Persistent but recoverable | Referenced customer not yet replicated | Publish to retry channel with delay 5 min, maximum 6 attempts |
| Poison | Schema-valid but semantically invalid payload | Publish to CH-03 dead-letter channel immediately |
| Deserialisation error | Unknown schema ID | Publish raw bytes to CH-03 with error header |
Dead-letter messages carry the original headers plus dlq_reason,
dlq_consumer, dlq_attempts and dlq_first_failure_time.
| DLQ process step | Rule |
|---|---|
| Alert | Any message in CH-03 pages the owning consumer team during business hours; more than [100] in 10 min pages on call |
| Triage | Within [1 business day] |
| Replay | Fix, then replay with the platform replay tool, preserving id so idempotency still applies |
| Discard | Only with a ticket recording the reason and approver |
9. Schema evolution and compatibility
9.1 Compatibility mode
The schema registry enforces [BACKWARD_TRANSITIVE / FULL_TRANSITIVE] compatibility for every subject in this specification: a consumer using any newer schema can read all data written with older schemas.
| Change | Allowed in the same major version? |
|---|---|
| Add an optional field with a default | Yes |
| Remove an optional field that has a default | Yes, after deprecation notice |
| Add a value to an enum | Yes, consumers must handle unknown values |
| Add a required field without a default | No |
| Rename a field or change its type | No |
| Change the meaning or unit of a field | No |
| Change the partition key | No; new channel version |
9.2 Breaking changes
A breaking change creates a new major event type (for example com.example.fulfilment.shipment.dispatched.v2) on a new
channel. The producer dual-publishes v1 and v2 for at least
[6 months]; v1 is then sunset after every registered consumer confirms
migration.
9.3 Deprecation of fields
Mark deprecated fields with "deprecated": true in the schema doc string,
announce in the changelog, keep populating them for [3 months], then
remove only if the compatibility mode allows it.
10. Security
| Control | Rule |
|---|---|
| Transport | TLS 1.2 or later between clients and brokers and between brokers |
| Authentication | Example: SASL/OAUTHBEARER with workload identity; no shared passwords |
| Authorisation | ACLs per principal: producer has WRITE on CH-01 and CH-02 only; each consumer group has READ on named channels |
| Personal data | Example: No names, addresses or phone numbers in payloads; consumers fetch them from the Customer API when authorised |
| Encryption at rest | Broker disks encrypted with provider-managed keys; [field-level encryption for proofOfDelivery image URL] |
| Integrity | Schemas validated at produce time; unknown producers rejected by ACL |
| Audit | Broker audit log of ACL changes and authentication failures retained [1 year] |
11. AsyncAPI document
The authoritative machine-readable contract is [asyncapi/fulfilment.yaml].
The skeleton shows the AsyncAPI 3.0 structure used here.
asyncapi: 3.0.0
info:
title: Fulfilment Events
version: 1.2.0
description: Shipment lifecycle events published by fulfilment-service.
defaultContentType: application/json
servers:
prod:
host: broker-1.prod.example.com:9093
protocol: kafka
security:
- $ref: '#/components/securitySchemes/oauth'
channels:
shipment:
address: fulfilment.shipment.v1
messages:
shipmentDispatched:
$ref: '#/components/messages/ShipmentDispatched'
operations:
publishShipmentDispatched:
action: send
channel:
$ref: '#/channels/shipment'
messages:
- $ref: '#/channels/shipment/messages/shipmentDispatched'
components:
securitySchemes:
oauth:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://auth.example.com/oauth2/token
availableScopes:
events:fulfilment:read: Read fulfilment events
messages:
ShipmentDispatched:
name: ShipmentDispatched
contentType: application/json
headers:
type: object
properties:
ce_id: {type: string}
ce_type: {type: string, const: com.example.fulfilment.shipment.dispatched.v1}
ce_time: {type: string, format: date-time}
payload:
$ref: '#/components/schemas/ShipmentDispatchedPayload'
bindings:
kafka:
key:
type: string
description: orderId
schemas:
ShipmentDispatchedPayload:
type: object
required: [shipmentId, orderId, carrier, trackingNumber, dispatchedAt, parcelCount]
properties:
shipmentId: {type: string}
orderId: {type: string}
carrier: {type: string}
trackingNumber: {type: string}
dispatchedAt: {type: string, format: date-time}
parcelCount: {type: integer, minimum: 1}
estimatedDeliveryDate: {type: string, format: date}
12. Interaction flows
The main flow from a carrier scan to downstream reactions, including the outbox relay and a consumer failure routed to the dead-letter channel:
| Scenario | Producer events | Expected consumer reaction |
|---|---|---|
| Example: Normal delivery | Created, Dispatched, Delivered | Billing invoices on Dispatched; Notification emails on each |
| Example: Failed delivery | Created, Dispatched, Failed | Notification emails customer; Support case opened |
| [Scenario] | [Events] | [Reaction] |
13. Operations and service levels
| Indicator | Objective | Measured as |
|---|---|---|
| Publish latency | p99 at or below 5 s from state change to broker | Outbox row timestamp to broker timestamp |
| Producer availability | 99.9% of events published within 60 s, monthly | Outbox backlog age |
| Broker availability | [99.95%] monthly | Platform SLO |
| Consumer lag (per consumer, owned by consumer) | Below [60 s] p95 | Lag exporter |
| [Indicator] | [Objective] | [Method] |
Replay: consumers may reset offsets within the retention window. Requests to re-publish older history go to [@team-fulfilment] with [5 business days] notice.
14. Consumer onboarding
| Step | Action | Owner | Done when |
|---|---|---|---|
| 1 | Read this specification and the AsyncAPI file | Consumer team | Questions raised in [channel] |
| 2 | Register as a consumer: name, contact, channels, purpose, data needs | Consumer team | Entry in consumer registry |
| 3 | Request ACLs and a consumer group ID (<team>.<app>.<purpose>) | Platform team | ACL applied in staging |
| 4 | Implement idempotency, DLQ and unknown-field handling per section 7 and 8 | Consumer team | Code review checklist passed |
| 5 | Run contract tests against registry schemas | Consumer team | CI green |
| 6 | Load test at [2x] expected peak in staging | Consumer team | Lag stays below objective |
| 7 | Add lag and DLQ alerts to the team dashboard | Consumer team | Alerts fire in a test |
| 8 | Production ACL approval | Producer owner and Security | ACL applied in production |
Registered consumers:
| Consumer | Team | Channels | Contact | Since |
|---|---|---|---|---|
Example: billing-service | Billing | CH-01 | [@billing-oncall] | YYYY-MM-DD |
| [Consumer] | [Team] | [Channels] | [@contact] | YYYY-MM-DD |
15. Glossary and references
15.1 Glossary
| Term | Meaning |
|---|---|
| Channel | Addressable destination for messages, such as a topic or queue |
| Operation | What an application does on a channel: send or receive |
| Envelope | Metadata wrapped around a payload (here CloudEvents attributes) |
| Partition key | Value that decides which partition receives a message and therefore its ordering scope |
| Outbox | Table written in the same transaction as business state, later relayed to the broker |
| Dead-letter queue (DLQ) | Channel holding messages that could not be processed |
| Compatibility mode | Registry rule deciding which schema changes are accepted |
| [Term] | [Meaning] |
15.2 References
| Reference | Used for |
|---|---|
| AsyncAPI Specification 3.0 | Structure of sections 2 to 5 and 11 |
| CloudEvents Specification 1.0 (CNCF) | Envelope attributes in 5.1 |
| W3C Trace Context | traceparent propagation |
| RFC 3339 | Timestamps |
| [Schema registry documentation] | Compatibility modes |
| [Internal messaging standard] | [Use] |
Standard and licence note
Structure aligned with the AsyncAPI Specification 3.0 (Apache License 2.0): servers, channels, operations, messages and reusable components, with the CloudEvents 1.0 envelope (CNCF, Apache License 2.0). Based on common industry practice for event-driven integration, including the transactional outbox and dead-letter channels. This template is original text written for this collection and reproduces no text from those specifications; read them for their normative requirements. Template licence: MIT.
Plantillas relacionadas
REST API Specification Template (OpenAPI 3.2, RFC 9457)
HTTP/JSON API contract: auth, conventions, QUERY, streaming, pagination, RFC 9457 errors, endpoints, webhooks, OpenAPI 3.2 skeleton
Arquitectura y diseño de sistemasAPI Versioning and Deprecation Policy Template
API lifecycle policy: versioning scheme, compatibility promises, breaking changes, timelines, Deprecation and Sunset headers
Arquitectura y diseño de sistemasAPI Design Guidelines Template (Company Style Guide)
Company API style guide: naming, methods, status codes, RFC 9457 errors, pagination, versioning, security, webhooks, governance