Resource library

QA How-To

Pact vs AsyncAPI for Event Contracts (2026)

Compare pact vs asyncapi for event contracts through runnable examples, CI checks, trade-offs, and practical guidance for choosing one tool or using both.

18 min read | 2,679 words

TL;DR

Pact and AsyncAPI solve different parts of event contract quality. Choose Pact for consumer-driven executable verification, AsyncAPI for a system-level event API definition, and both when you need discoverability plus proof that real provider code satisfies consumer expectations.

Key Takeaways

  • Use Pact when a specific consumer needs executable proof that a provider still emits a compatible message.
  • Use AsyncAPI when teams need a shared, broker-aware description of channels, operations, messages, and schemas.
  • Pact verifies examples and provider behavior, while AsyncAPI validation verifies the correctness of an interface document.
  • Use both when documentation, governance, and consumer-driven compatibility are all release requirements.
  • Keep event metadata, optionality, and versioning rules explicit because payload-only checks miss common integration failures.
  • Run contract checks before deployment and publish only artifacts produced from a passing commit.

Pact vs AsyncAPI for event contracts is not a simple tool replacement decision. Pact answers whether a named provider can produce the message a real consumer expects, while AsyncAPI describes the event interface across channels, operations, bindings, messages, and reusable schemas. Use Pact for consumer-driven compatibility checks, AsyncAPI for documentation and governance, and combine them when both release confidence and cross-team discoverability matter.

This guide builds the same OrderCreated contract in both tools. You will validate an AsyncAPI document, generate and verify a Pact message contract, and see exactly what each failure proves. For broader context, read the contract testing guide and the event-driven microservices testing guide.

TL;DR

Decision factor Pact message contracts AsyncAPI
Primary purpose Verify provider behavior against consumer expectations Describe an asynchronous API as a shared interface
Contract owner Usually starts with a consumer test Usually platform or API owners collaborate with producers and consumers
Executable against provider code Yes, through provider verification Not by the specification alone
Broker and channel modeling Limited; metadata can be asserted First-class channels, operations, protocols, and bindings
Documentation Pact Broker shows relationships and verification state Rich API documentation can be generated from the document
Compatibility signal Interaction-specific and tied to participants Schema and document validation, with compatibility requiring extra policy or tooling
Best fit Preventing a producer change from breaking known consumers Standardizing and explaining an event ecosystem
Strongest setup Pact Broker plus CI provider verification AsyncAPI validation plus linting, schema compatibility, and runtime tests

The practical verdict is direct: Pact gives stronger evidence about a provider-consumer relationship. AsyncAPI gives a more complete architectural description. Neither automatically proves end-to-end delivery through Kafka, RabbitMQ, or another broker.

What You Will Build

You will create a small TypeScript project containing:

  • An AsyncAPI 3.0 document for an orders.created channel.
  • A JSON Schema for the OrderCreated payload embedded in that document.
  • A Pact consumer test that records the exact message the consumer accepts.
  • A Pact provider verification test that calls real provider code.
  • CI-friendly commands with an observable success condition at every step.

The example deliberately includes message metadata. Event integrations often fail because a consumer assumes a content type, event name, or correlation identifier that the payload schema never mentions.

Prerequisites

Use Node.js 20 or newer and npm. The commands use tsx to run TypeScript, Vitest as the test runner, Pact JS for message contracts, and the official AsyncAPI CLI for document validation. Pin versions in your lockfile rather than copying floating versions into a production pipeline.

mkdir event-contract-demo
cd event-contract-demo
npm init -y
npm install -D typescript tsx vitest @types/node @pact-foundation/pact @asyncapi/cli
npx tsc --init --module nodenext --moduleResolution nodenext --target es2022 --strict

Add scripts to package.json:

{
  "scripts": {
    "test": "vitest run",
    "contract:asyncapi": "asyncapi validate asyncapi.yaml",
    "contract:pact:consumer": "vitest run test/order-consumer.pact.test.ts",
    "contract:pact:provider": "vitest run test/order-provider.pact.test.ts"
  }
}

Verify the toolchain before writing a contract:

node --version
npx asyncapi --version
npx vitest --version

Expect a Node major version of at least 20 and successful version output from both CLIs. If npm reports native-library installation trouble for Pact, confirm that your operating system and CPU are supported by the installed Pact package before changing test code.

1. Pact vs AsyncAPI for Event Contracts: The Conceptual Difference

AsyncAPI is an interface description. Its document can say that an application sends an OrderCreated message to orders.created, that Kafka is the protocol, and that the payload follows a particular schema. This contract is useful before any single consumer exists. Reviewers can discuss naming, channel topology, security, bindings, and reusable domain schemas in one artifact.

Pact is an interaction contract. A consumer test states, in executable form, the subset of fields and metadata that the consumer uses. Pact writes that expectation to a pact file. Provider verification then invokes provider code and checks the produced message against the interaction. A broker can store contracts, record verification results, and help a deployment workflow answer whether a participant version is safe to release.

That ownership difference changes failure meaning. An invalid AsyncAPI file proves the document violates specification or schema rules. It does not prove that deployed producer code emits matching bytes. A failed Pact provider verification proves that the provider code under test cannot satisfy a recorded consumer interaction. It does not prove that every event in the ecosystem is documented or that broker permissions and routing are correct.

Think in layers: AsyncAPI defines the public map; Pact checks selected roads used by real travelers. Broker integration tests still confirm that messages are serialized, published, routed, retained, and consumed correctly.

Step 1: Define the Shared Event Shape with AsyncAPI

Create asyncapi.yaml. AsyncAPI 3.0 separates channels from operations, and an operation points to a channel with a reference. This example describes the producer perspective: the application sends an event to the broker channel.

asyncapi: 3.0.0
info:
  title: Orders Event API
  version: 1.0.0
  description: Events emitted by the orders service.
defaultContentType: application/json
servers:
  production:
    host: kafka.example.com:9092
    protocol: kafka
channels:
  ordersCreated:
    address: orders.created
    messages:
      orderCreated:
        $ref: '#/components/messages/OrderCreated'
operations:
  publishOrderCreated:
    action: send
    channel:
      $ref: '#/channels/ordersCreated'
    messages:
      - $ref: '#/channels/ordersCreated/messages/orderCreated'
components:
  messages:
    OrderCreated:
      name: OrderCreated
      title: Order created event
      contentType: application/json
      headers:
        type: object
        required: [eventType, correlationId]
        properties:
          eventType:
            const: OrderCreated
          correlationId:
            type: string
            minLength: 1
      payload:
        type: object
        additionalProperties: false
        required: [eventId, orderId, customerId, totalCents, currency, occurredAt]
        properties:
          eventId:
            type: string
            format: uuid
          orderId:
            type: string
            minLength: 1
          customerId:
            type: string
            minLength: 1
          totalCents:
            type: integer
            minimum: 0
          currency:
            type: string
            pattern: '^[A-Z]{3}
#39; occurredAt: type: string format: date-time

Validate the document:

npm run contract:asyncapi

The command should exit with status 0 and identify asyncapi.yaml as valid. Now change action: send to action: publish and run it again. AsyncAPI 3.0 permits send or receive, so validation should fail. Restore send before continuing. This controlled mutation confirms the command is actually checking the document rather than merely parsing YAML.

Step 2: Implement the Consumer at the Boundary

The consumer should translate the external event into an internal action. Keeping that boundary small makes the contract meaningful and prevents a Pact test from becoming a broad unit test. Create src/orderConsumer.ts:

export type OrderCreated = {
  eventId: string;
  orderId: string;
  customerId: string;
  totalCents: number;
  currency: string;
  occurredAt: string;
};

export type IndexOrder = (order: {
  id: string;
  customerId: string;
  amount: number;
  currency: string;
}) => Promise<void>;

export function createOrderCreatedHandler(indexOrder: IndexOrder) {
  return async (event: OrderCreated): Promise<void> => {
    if (event.totalCents < 0) throw new Error('totalCents must be non-negative');
    await indexOrder({
      id: event.orderId,
      customerId: event.customerId,
      amount: event.totalCents,
      currency: event.currency
    });
  };
}

Add test/orderConsumer.unit.test.ts:

import { describe, expect, it, vi } from 'vitest';
import { createOrderCreatedHandler } from '../src/orderConsumer.js';

describe('OrderCreated handler', () => {
  it('indexes a valid order', async () => {
    const indexOrder = vi.fn().mockResolvedValue(undefined);
    const handle = createOrderCreatedHandler(indexOrder);
    await handle({
      eventId: 'a5cc5dc6-8f4b-4f13-b03a-17fc9d458f25',
      orderId: 'order-123',
      customerId: 'customer-9',
      totalCents: 2599,
      currency: 'USD',
      occurredAt: '2026-08-06T10:00:00Z'
    });
    expect(indexOrder).toHaveBeenCalledWith({
      id: 'order-123', customerId: 'customer-9', amount: 2599, currency: 'USD'
    });
  });
});

Verify the boundary:

npx vitest run test/orderConsumer.unit.test.ts

Expect one passing test. This proves the handler uses the fields you intend to place in the consumer contract. It says nothing yet about what the producer emits.

Step 3: Create a Pact Message Consumer Contract

Create test/order-consumer.pact.test.ts. MessageConsumerPact records an asynchronous message interaction. Matchers express the acceptable shape without freezing every generated value to one example.

import path from 'node:path';
import { describe, it } from 'vitest';
import { MatchersV3, MessageConsumerPact } from '@pact-foundation/pact';
import { createOrderCreatedHandler } from '../src/orderConsumer.js';

const { like, regex, uuid, integer } = MatchersV3;

const pact = new MessageConsumerPact({
  consumer: 'search-indexer',
  provider: 'orders-service',
  dir: path.resolve('pacts'),
  logLevel: 'warn'
});

describe('OrderCreated message contract', () => {
  it('accepts the event used by search-indexer', async () => {
    await pact
      .expectsToReceive('an order created event')
      .withMetadata({
        contentType: 'application/json',
        eventType: 'OrderCreated',
        correlationId: like('corr-123')
      })
      .withContent({
        eventId: uuid('a5cc5dc6-8f4b-4f13-b03a-17fc9d458f25'),
        orderId: like('order-123'),
        customerId: like('customer-9'),
        totalCents: integer(2599),
        currency: regex('^[A-Z]{3}
#39;, 'USD'), occurredAt: regex( '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z
#39;, '2026-08-06T10:00:00Z' ) }) .verify(async (message) => { const indexOrder = async () => undefined; const handle = createOrderCreatedHandler(indexOrder); await handle(message.contents as Parameters<typeof handle>[0]); }); }); });

Run the consumer contract test:

npm run contract:pact:consumer
test -f pacts/search-indexer-orders-service.json

Both commands should exit successfully. Open the pact JSON and confirm it contains the description an order created event, message contents, matching rules, and metadata. Do not hand-edit this generated file. The consumer test is its source. For a deeper consumer workflow, see API contract testing with Pact.

Step 4: Implement the Provider Message Factory

Provider verification needs a deterministic function that builds the message for a named provider state. Create src/orderProvider.ts:

export type OrderRecord = {
  id: string;
  customerId: string;
  totalCents: number;
  currency: string;
  createdAt: string;
};

export function toOrderCreated(order: OrderRecord) {
  return {
    contents: {
      eventId: 'a5cc5dc6-8f4b-4f13-b03a-17fc9d458f25',
      orderId: order.id,
      customerId: order.customerId,
      totalCents: order.totalCents,
      currency: order.currency,
      occurredAt: order.createdAt
    },
    metadata: {
      contentType: 'application/json',
      eventType: 'OrderCreated',
      correlationId: `order-${order.id}`
    }
  };
}

Add test/orderProvider.unit.test.ts:

import { expect, it } from 'vitest';
import { toOrderCreated } from '../src/orderProvider.js';

it('maps the stored order to an event', () => {
  const message = toOrderCreated({
    id: 'order-123', customerId: 'customer-9', totalCents: 2599,
    currency: 'USD', createdAt: '2026-08-06T10:00:00Z'
  });
  expect(message.contents.orderId).toBe('order-123');
  expect(message.metadata.eventType).toBe('OrderCreated');
});

Verify the producer mapping:

npx vitest run test/orderProvider.unit.test.ts

Expect one passing test. In a real service, use a fixture repository or provider state setup to supply the order. Avoid calling production infrastructure during contract verification because nondeterminism makes compatibility failures hard to diagnose.

Step 5: Verify the Pact Against Provider Code

Create test/order-provider.pact.test.ts. MessageProviderPact reads the generated pact and associates the interaction description with the provider function that produces that message.

import path from 'node:path';
import { describe, it } from 'vitest';
import { MessageProviderPact } from '@pact-foundation/pact';
import { toOrderCreated } from '../src/orderProvider.js';

describe('orders-service Pact verification', () => {
  it('satisfies search-indexer expectations', async () => {
    const verifier = new MessageProviderPact({
      provider: 'orders-service',
      pactUrls: [path.resolve('pacts/search-indexer-orders-service.json')],
      messageProviders: {
        'an order created event': () => toOrderCreated({
          id: 'order-123',
          customerId: 'customer-9',
          totalCents: 2599,
          currency: 'USD',
          createdAt: '2026-08-06T10:00:00Z'
        })
      },
      logLevel: 'warn'
    });
    await verifier.verify();
  });
});

Run consumer generation first, then provider verification:

npm run contract:pact:consumer
npm run contract:pact:provider

Expect both Vitest runs to pass. To prove the provider check is sensitive, temporarily rename totalCents to total in toOrderCreated and rerun the provider script. The verifier should report the missing expected field. Restore the implementation afterward. This is the core Pact advantage: the failure connects an actual provider output to a known consumer expectation.

If Kafka is your transport, continue with Kafka consumer contract testing step by step. Pact verifies message creation and handling, while a broker test verifies serializer configuration, headers, topic routing, and client behavior.

6. Pact vs AsyncAPI for Event Contracts in CI

Put fast document and contract checks before packaging. A minimal pipeline runs installation, AsyncAPI validation, the consumer contract, and provider verification in that order:

name: event-contracts
on:
  pull_request:
  push:
    branches: [main]
jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run contract:asyncapi
      - run: npm run contract:pact:consumer
      - run: npm run contract:pact:provider

Verification is straightforward: open the pull request check and require the verify job to be green before merge. For separate repositories, the consumer publishes its pact to a Pact Broker, and the provider retrieves relevant pacts during verification. Publish verification results with the provider version and branch supplied by the CI environment. Deployment checks should query broker compatibility rather than copy pact files manually between repositories.

AsyncAPI also deserves more than syntax validation. Add a linter with organization rules for channel naming, required descriptions, message names, identifiers, and security declarations. If schema compatibility matters, compare the proposed schema against the released schema with a schema registry or a dedicated compatibility checker. Plain asyncapi validate verifies document validity; it does not promise backward compatibility between two valid documents.

Publish the AsyncAPI document and Pact artifacts only from a successful commit. Otherwise, consumers may discover a contract that never passed the code producing it.

7. What Each Approach Misses

Pact can accumulate narrow interactions without giving architects a coherent inventory. If three consumers require three subsets of OrderCreated, their pacts correctly represent those relationships, but they do not naturally document every channel, server, binding, security scheme, or operation in the event platform. A consumer that never writes a Pact remains invisible to Pact-based release decisions.

AsyncAPI can become aspirational. A beautiful document may differ from serialization code because specification validation does not execute the producer. JSON Schema also has nuances: format: date-time may be treated as annotation by some validators unless format assertion is enabled. Broker-specific headers and keys need explicit bindings or headers, not assumptions hidden in application code.

Both approaches miss operational behavior unless you test it separately. Neither proves that access control permits publishing, partitions preserve the ordering your consumer expects, retry policy avoids duplicates, or a dead-letter route retains failed messages. Add integration scenarios for delivery, redelivery, idempotency, ordering, malformed payloads, and observability. The microservices performance testing guide is useful when throughput and backlog behavior become acceptance criteria.

The right question is therefore not which artifact is more complete in isolation. Ask which risk must block the release, which team owns the evidence, and how quickly a failure points to the responsible code or definition.

8. Schema Evolution and Versioning

For backward-compatible evolution, add optional fields and keep the meaning of existing fields stable. A new optional discountCents property can be harmless if older consumers ignore unknown fields. However, this tutorial's AsyncAPI schema sets additionalProperties: false, so adding that property requires updating the schema first. Pact consumers only care about the fields and matchers in their interaction, making additive provider changes easier when serializers and handlers tolerate them.

Removing a field, changing totalCents from integer to string, or changing currency from ISO-like uppercase codes to free text is potentially breaking. Pact exposes the consumers that exercise those assumptions. AsyncAPI exposes the declared public shape and supports review across consumers not represented in the broker.

Do not put a version in a topic name for every additive change. Version the message or channel when semantics become incompatible and migration requires old and new forms to coexist. Document deprecation dates, owners, and consumer migration status. Keep correlation and event identifiers stable because replay and deduplication depend on them.

A useful release rule combines three signals: the new AsyncAPI document passes organizational validation, changed schemas satisfy the chosen compatibility policy, and every relevant Pact provider verification is green. That layered rule catches structural, historical, and behavioral incompatibility.

Which Should You Choose

Choose Pact when your immediate risk is breaking a known consumer. It fits independently deployed services, especially when consumer teams can state minimal expectations and provider teams can verify them before release. A Pact Broker adds visibility into participant versions and verification results. Pact is less attractive when no consumer can own tests or when the main need is an enterprise event catalog.

Choose AsyncAPI when your immediate need is a canonical event interface. It is the stronger choice for design reviews, onboarding, channel discovery, protocol details, code or documentation generation, and governance across many teams. Pair validation with compatibility checks and runtime conformance tests if release safety depends on the document matching code.

Choose both for mature event platforms. Start with AsyncAPI to define the public operation and message vocabulary. Let each important consumer record the subset it genuinely uses in Pact. Verify provider code against those pacts, and run broker integration tests for delivery guarantees. Avoid mechanically generating every Pact from AsyncAPI because that loses consumer intent. Also avoid treating consumer pacts as the sole architectural catalog because they only describe observed relationships.

For comparison with request-response specifications, read Pact vs OpenAPI for contract testing. The ownership lesson is similar, but asynchronous transports add metadata, routing, ordering, and delivery semantics that HTTP contracts do not model in the same way.

Common Mistakes

  • Treating AsyncAPI validation as runtime conformance. A valid file proves its own structure. Add producer tests that serialize real events and validate them against the released schema.
  • Writing an exact Pact example for every dynamic value. Use matchers for UUIDs, timestamps, identifiers, and constrained strings. Exact matching on generated values creates false failures.
  • Ignoring metadata. Content type, event type, correlation ID, partition key, and schema ID can be as important as payload fields. Model and verify the metadata the consumer reads.
  • Publishing stale contracts. Tie artifacts and verification results to immutable commit identifiers. Never overwrite the evidence for one build with output from another.
  • Confusing send with receive. AsyncAPI operation actions are relative to the application described by the document, not the broker. State the perspective in reviews.
  • Making every property required. Required fields constrain evolution. Require only what consumers and domain invariants truly need, and test missing optional data.
  • Skipping negative and replay tests. Contract success does not prove idempotency or failure handling. Exercise duplicate events, invalid messages, delayed delivery, and dead-letter recovery through the real broker.
  • Sharing one giant contract among unrelated consumers. Pact contracts should expose each consumer's dependency. A monolithic expectation hides which team blocks a provider change.

Troubleshooting

AsyncAPI reports an invalid action -> Confirm the document uses AsyncAPI 3.0 semantics. Operations use action: send or action: receive, from the described application's viewpoint.

The Pact provider cannot find the interaction -> Make the key in messageProviders exactly match the consumer test's expectsToReceive description. Also confirm the pact path resolves from the process working directory.

Provider verification fails only on metadata -> Compare key spelling and value types. Messaging clients may expose native headers as buffers, so normalize them at the adapter boundary before returning the message to the verifier.

A timestamp matcher rejects a valid-looking value -> Inspect the regex stored in the pact and the actual serialized timestamp. Standardize on UTC RFC 3339 output, such as 2026-08-06T10:00:00Z, unless the domain explicitly permits offsets.

A new field breaks schema validation -> Check additionalProperties. If it is false, add the field to the AsyncAPI schema and apply your compatibility policy before publishing producer code.

Contract checks pass but Kafka consumption fails -> Test the actual serializer, headers, authentication, topic name, key, and consumer group through a broker environment. Pact message verification does not connect to Kafka.

Interview Questions and Answers

Pact and AsyncAPI interviews often test whether you distinguish documentation, compatibility, and transport behavior. The interviewQnA section below contains concise model answers covering ownership, provider verification, schema evolution, metadata, and CI. A strong answer names the evidence each tool produces instead of claiming that either tool replaces broker integration testing.

Where To Go Next

Run the example once, then introduce a breaking provider change and read both failure reports. That exercise makes the different feedback loops concrete. Next, add a schema compatibility policy, publish pacts to a broker, and create one broker-backed smoke test for serialization and routing.

Use the contract testing interview questions for microservices to practice explaining the strategy. Then compare your implementation with testing backend contracts without production. Those guides extend the same principle: obtain reliable compatibility evidence before a risky environment becomes the first place a mismatch is visible.

Conclusion

Pact vs AsyncAPI for event contracts has a layered answer. Pact is the better executable safety net for a concrete consumer-provider relationship. AsyncAPI is the better shared description of an event API and its channels, operations, messages, and protocol context.

Use the smallest combination that blocks your real failure modes. For most independently deployed event systems, that means AsyncAPI validation for the public contract, Pact verification for important consumer expectations, and a focused broker test for delivery behavior. Keep all three tied to the same commit so the documentation, compatibility evidence, and running code tell one consistent story.

Interview Questions and Answers

What is the main difference between Pact and AsyncAPI for event contracts?

Pact is consumer-driven and executable: a consumer records an expected message and the provider verifies its code against that interaction. AsyncAPI is an interface description for channels, operations, messages, schemas, servers, and protocol details. Pact provides relationship-specific compatibility evidence, while AsyncAPI provides a system-level contract and documentation source.

What does a passing Pact message provider verification prove?

It proves that the provider function exercised by the verifier produced content and metadata matching the selected consumer interactions. It does not prove that a broker accepted or delivered the message. Transport configuration, serialization, routing, security, and delivery semantics need integration coverage.

Why is a valid AsyncAPI document not enough for release confidence?

Document validation checks specification structure and schema validity, not the bytes emitted by provider code. The implementation can drift from the document while both compile independently. Add producer conformance tests, schema compatibility checks, or Pact provider verification to connect the definition to behavior.

How would you test a backward-compatible event change?

First validate the updated AsyncAPI document and compare its schema with the released version under an explicit compatibility policy. Then regenerate affected consumer pacts and verify the provider against all relevant interactions. Finally, run a broker-backed test if headers, serialization, routing, or delivery behavior changed.

Why should Pact message tests use matchers?

Matchers describe the allowed category of values rather than locking a contract to one generated UUID, timestamp, or identifier. They reduce false failures while retaining constraints the consumer depends on. Exact values remain appropriate for discriminators such as a fixed event type.

How do you prevent AsyncAPI and Pact contracts from drifting apart?

Run both checks in CI against the same commit and publish artifacts only after they pass. Share domain schema sources where practical, but preserve consumer-authored Pact intent rather than generating every interaction mechanically. Add a producer conformance test that validates a serialized event against the AsyncAPI payload schema.

When would you choose AsyncAPI without Pact?

I would choose AsyncAPI alone when the primary need is design governance, documentation, discovery, or code generation and there are no independently deployed consumers able to own Pact tests. I would still add schema compatibility and runtime conformance checks if producer changes can break users.

Frequently Asked Questions

Is AsyncAPI a replacement for Pact message contract testing?

No. AsyncAPI describes an asynchronous API, while Pact verifies that provider code can produce a message expected by a specific consumer. AsyncAPI validation alone does not execute the provider implementation.

Can Pact test Kafka messages?

Pact can verify the content and metadata of messages created and consumed by application code. It does not publish through Kafka during a message contract test, so use a separate broker integration test for topics, keys, serialization, authentication, ordering, and delivery behavior.

Should a team use Pact and AsyncAPI together?

Yes, when it needs both a discoverable event API definition and executable consumer-provider compatibility evidence. Keep AsyncAPI as the public system contract, then let consumers express their used subsets through Pact interactions.

Does asyncapi validate prove schema backward compatibility?

No. The validation command proves that a document conforms to the AsyncAPI specification and referenced schema rules. Comparing a proposed schema with a released version requires an explicit compatibility checker, registry policy, or equivalent review gate.

Who should own an event contract?

Ownership is shared but responsibilities differ. A producer or platform team usually maintains the AsyncAPI definition, consumer teams own their Pact expectations, and the provider team owns verification against provider code.

What event metadata belongs in a contract?

Include every metadata value that affects processing, such as content type, event type, correlation ID, partition key, schema identifier, and tenant identifier. Do not assert transport details that the application never reads unless they are an explicit platform requirement.

How should event schemas evolve without breaking consumers?

Prefer optional additive fields, preserve the meaning and type of existing fields, and verify important consumers before release. Use a new message or channel version when semantics are incompatible and old and new consumers must coexist.

Related Guides