awesome-everything RU
↑ Back to the climb

Engineering Practice

Provider verification and the broker: replaying the consumer''''s expectations against the real service

Crux The consumer published a pact; now the provider must replay every recorded interaction and prove each one holds. Provider states set up the data so an interaction is satisfiable. The broker stores pacts plus results and, via webhooks, decouples the two CI pipelines.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 17 min

Priya’s checkout-web team published a pact: “when I GET /prices/42, I read amount_cents (a number) and currency (a string).” The contract now exists. But a contract no one checks is just a wish. Sam owns the pricing service, and last sprint he renamed amount_cents to unit_amount — a clean refactor, all of pricing’s own tests green, deployed Friday. On Monday checkout-web rendered NaN on every product page because the field it parses had silently vanished. The pact described exactly the breakage that shipped, and nothing replayed it against Sam’s real service before he deployed. The recording was never played back. That playback — sending each recorded request at the actual running provider and checking the response still honours what the consumer reads — is provider verification, and the place that stores the pacts and the verification results so the two teams never have to coordinate by hand is the broker.

Verification is replay, not re-reading

The pact file is a list of concrete interactions: request, expected response, and the provider state each one assumes. Provider verification is mechanical: a Pact verifier takes that list and, for each interaction, sends the recorded request to the real, running provider and compares the actual response against the expected one. No mock is involved on the provider side — the request hits the genuine routing, controllers, serializers, and (with state set up) the genuine data layer. If checkout-web’s pact says GET /prices/42 should return a body containing amount_cents, the verifier fires that exact request at the booted pricing service and inspects what comes back.

The comparison is deliberately asymmetric, and this is the property that makes the gate stable. The verifier checks that the actual response contains at least the data the consumer described — the minimal expected response. Sam can add ten new fields to /prices/42 and verification stays green, because the consumer never asserted their absence. But if he renames amount_cents, the field the consumer reads is gone, the actual response no longer contains the minimal expected shape, and verification fails before he deploys. The contract trips on exactly the change that would break a real consumer and stays silent on everything else, which is the same precision the consumer-driven recording bought you, now enforced from the provider’s side.

Provider states are the subtle hard part

An interaction like “GET /prices/42 returns 200 with a price body” is only satisfiable if price 42 actually exists in the provider when the request is replayed. The consumer recorded this precondition as a provider state — a string in the given clause, e.g. "a price with id 42 exists", the Pact equivalent of Cucumber’s Given step: put the system in a known state before the interaction runs. Verification is the moment that string has to be turned into real data.

So the provider must register state handlers: code that, for each named state, sets up the data the interaction needs. Before replaying each interaction, the verifier invokes the matching handler (often by POSTing { "consumer": "...", "state": "..." } to a test-only state-change endpoint), the handler inserts price 42 into the provider’s data store, then the request is sent. Two rules make this work and are easy to get wrong. First, each interaction is verified in isolation — no state carries over from the previous one, so every handler must establish its full precondition from scratch and ideally tear down after. Second, the handler sets up data, not the response: it makes the interaction satisfiable, but the real provider code still produces the response, which is the whole point. This is where verification quietly gets expensive — every distinct given string the consumers wrote becomes a state handler the provider team must implement and maintain, and a missing or wrong handler fails verification not because the contract is broken but because the precondition was never met.

Why this works

Why not just point verification at a shared staging database that already has price 42 in it? Because then the test is no longer deterministic or isolated: whether GET /prices/42 returns 200 depends on whatever rows happen to exist that day, another team’s cleanup job can delete price 42 between runs, and a 404-expecting interaction can’t coexist with a 200-expecting one against the same fixed data. Provider states move the precondition into the contract and into provider-owned setup code, so each interaction carries its own world and is replayable in isolation on a freshly seeded provider. That is exactly what lets verification run in the provider’s own CI, against its own ephemeral data, with no coordination with the consumer’s environment.

The broker is the system of record between two pipelines

Verification needs two artifacts to meet: the consumer’s pact and the provider’s pass/fail result. The naive way to make them meet is to wire the consumer’s CI to trigger the provider’s build directly — but that re-couples the two pipelines you went to contract testing to decouple. The Pact Broker (open-source) / PactFlow (hosted) is the exchange that breaks the coupling. The consumer publishes its pact to the broker, tagged with its version and branch, and walks away. Independently, the provider’s CI pulls the relevant pacts from the broker, runs verification, and publishes the results back. Neither pipeline calls the other; the broker is the shared, asynchronous system of record holding every pact and every verification result.

The broker can also push. A webhook fires on broker events — most importantly contract_requiring_verification_published (which superseded contract_content_changed in broker 2.82.0) — to trigger the provider’s verification build the moment a new or changed pact lands, passing templated parameters like ${pactbroker.pactUrl} and ${pactbroker.providerVersionNumber} so the provider build knows exactly what to verify. The result of all this is the verification results matrix: a grid of which consumer versions have been verified against which provider versions. That matrix is what the can-i-deploy tool queries before a release — “is the version of pricing I’m about to ship verified against the version of checkout-web already in production?” — so each pacticipant (Pact’s word for an app in a contract) can deploy on its own schedule, gated by recorded results rather than by a human checking with the other team.

AspectNo broker: pipelines wired directlyBroker as system of record
Pact handoffCommitted to provider repo / passed by handPublished to broker, tagged version + branch
Who triggers verifyConsumer CI calls provider build directlyBroker webhook on contract-requiring-verification
Pipeline couplingTight — consumer build blocks on providerAsync — neither pipeline calls the other
Deploy decisionManual “is the other team’s build green?”can-i-deploy queries the results matrix
Independent deployNo — both must release togetherYes — each pacticipant on its own schedule
Order the steps

Order the end-to-end flow from a new consumer pact to a gated provider deploy:

  1. 1 Consumer CI publishes the pact to the broker, tagged with its version and branch
  2. 2 Broker fires a contract_requiring_verification_published webhook at the provider's CI
  3. 3 Provider boots, and for each interaction runs the state handler to set up the data its `given` assumes
  4. 4 The verifier replays each recorded request and checks the response contains the minimal expected shape
  5. 5 Verification results are published back to the broker, filling in the matrix
  6. 6 Before release, can-i-deploy queries the matrix to confirm this provider version is verified against prod's consumer
Quiz

During verification, the provider adds three new fields to GET /prices/42 that the consumer's pact never mentions. What happens, and why?

Quiz

An interaction's `given` is 'a price with id 42 exists', but the provider team never wrote a handler for that state. What's the result and the correct reading of it?

Pick the best fit

How should the provider get the data in place so each recorded interaction is satisfiable during verification?

Recall before you leave
  1. 01
    Explain the full provider-verification mechanism, including the minimal-response comparison and the role of provider states.
  2. 02
    What is the broker for, and how do webhooks plus can-i-deploy let two teams deploy independently?
Recap

The consumer published a pact, but a contract no one replays is just a wish — the rename that returned NaN shipped because nothing played the recording back against the real provider. Provider verification is that playback: the verifier sends each recorded request at the genuine running provider and checks the actual response contains at least the minimal expected shape, so the gate stays green when the provider adds fields no one reads and trips only when a field the consumer actually parses is renamed, removed, or retyped. The subtle hard part is provider states: each interaction declares its precondition as a given string, and the provider must register per-state handlers that seed the exact data — run before each interaction, in isolation, setting up data and not the response — because pointing verification at shared staging data destroys the determinism the contract is supposed to guarantee. Tying it together is the broker (Pact Broker or PactFlow), the asynchronous system of record holding every pact and every verification result so neither CI pipeline has to call the other; webhooks like contract_requiring_verification_published trigger the provider’s verify build when a pact changes, results accumulate into the verification matrix, and can-i-deploy queries that matrix so each pacticipant can deploy independently — gated by recorded proof rather than by two teams coordinating by hand. Next: how contracts evolve safely as both sides change over time.

Connected lessons
Continue the climb ↑can-i-deploy and contract versioning: the deployment-safety gate
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources5
expand
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05

Trademarks belong to their respective owners. Editorial reference only.