Credit where due: pact-stub-server does exactly what it says on the tin. It's the Pact Foundation's Rust tool that loads your Pact contract files and serves their interactions as HTTP stubs โ and it's actively maintained (v0.7.1 shipped June 2026, Pact specification v4 support). If your team runs Pact contract tests, it has genuinely nice machinery: it can fetch pacts straight from a Pact Broker (with bearer tokens and consumer/provider regex filters), -w watches pact files and hot-reloads them, and -s filters interactions by provider state so one server can play different scenarios. Nothing on this page changes any of that.
This page is about the moment a team points a frontend, a demo, or a manual QA session at it โ using the contract stub as a general-purpose dev backend. A pact file is a record of specific request/response pairs, and pact-stub-server replays it faithfully. Faithfully is the problem.
We loaded a plain two-interaction pact โ GET /products returning a two-record list and a POST /products with an exact body returning a canned 201 โ and drove it with curl. Every claim below is the actual observed behavior.
Wall 1: anything not in the pact is a 404 with an empty body. GET /products/1 โ the completely obvious single-record fetch next to our stubbed list โ answered 404 with content-length: 0. No JSON error payload, so res.json() throws before your error handling even reads the status. Every route, every ID, every edge case must literally appear in a pact file.
Wall 2: matching is exact unless the pact carries matching rules. GET /products?page=2 and ?limit=5 โ 404, because the recorded request had no query string. POST /products with {"name":"Other","price":7} โ 404, because the pact's body said {"name":"New thing","price":5}. (Pact matchers can loosen this โ but only if your consumer tests wrote them into the pact; hand-rolled or example-grade pacts rarely do. Extra request headers are fine โ we sent a surplus Authorization header and still matched.)
Wall 3: writes are theater. The matched POST /products returned its canned 201 {"id":3,โฆ} โ and the next GET /products returned the same two records as before. Nothing persists; the 201 is a recording. That's correct for a contract stub, and useless for a demo where you create something and expect to see it.
Wall 4: no failure simulation at all. We catalogued the full --help: log level, file/dir/url/broker loading, port, CORS, TLS, provider-state filtering, watch mode. There is no latency flag, no error-injection flag, no status override. Testing spinners, timeouts and retry logic is out of scope.
Wall 5: browser calls need a flag, and misses are undiagnosed. OPTIONS preflight returns 404 unless you remembered -o/--cors โ so a browser fetch() POSTing JSON from another origin fails before the request even happens. And when a request doesn't match, the log says only No matching request found for path /products โ not what differed (body? query?), so you're diffing pact JSON by eye.
The response bodies in your pact files are seed data. One jq line lifts them into a db.json-style import โ this exact pipeline ran against this API before publishing:
# pull the list-response body out of the pact, wrap it as {db:{products:[โฆ]}}:
jq '{name:"from-pact", db:{products:(.interactions[]
| select(.description|test("all products")) | .response.body)}}' \
pacts/web-app-products-api.json > db-from-pact.json
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/import \
-H 'content-type: application/json' -d @db-from-pact.json
# โ {"id":"<PID>","adminKey":"โฆ","baseUrl":"https://mockbird.mockbird.workers.dev/m/<PID>",โฆ}
Now the walls are just gone:
curl https://mockbird.mockbird.workers.dev/m/<PID>/products/1 # single GET: 200, free
curl 'https://mockbird.mockbird.workers.dev/m/<PID>/products?price_gte=15' # filters: free
curl -X POST https://mockbird.mockbird.workers.dev/m/<PID>/products \
-H 'content-type: application/json' -d '{"name":"Other","price":7}'
# โ {"name":"Other","price":7,"id":3} โ and GET /products now returns 3 records. It persists.
curl https://mockbird.mockbird.workers.dev/m/<PID>/products/999
# โ 404 with a JSON body: {"error":"not found"}
No process to run, CORS (including preflight) on by default, and the URL works from a teammate's laptop, a deployed preview, or CI.
| pact-stub-server | Mockbird | Notes |
|---|---|---|
| interaction response bodies | imported records | jq โ db.json import; ids/types inferred, data verbatim |
| one interaction per request variant | stateful resources | list/single/nested GETs, POST/PUT/PATCH/DELETE, filters/sort/pagination/search โ zero per-variant config |
provider states (-s, state header) | snapshots + X-Mockbird-Snapshot | same idea, per request: pin any request to a named data state; --empty-provider-state footgun doesn't exist |
-w watch + edit pact files | edit records via the API/dashboard | changes are live immediately; no files to reload |
-o/--cors | CORS default-on incl. preflight | verified: browser fetch works with no flag |
| exact request matching | match-free CRUD + _gte/_lte/_ne/_like | unknown paths are real 404s with JSON bodies |
| failure drills | ?mock_status=503, ?mock_delay=2000, ?mock_chaos=0.3, ?mock_seq=500,500,200 | they have no flags for this โ guide |
| fixed/odd payloads | custom routes | any status/body/content-type/headers/delay + templating |
| Pact Broker fetch, token auth, consumer/provider filters | no equivalent | honest: if your pacts live in a broker, that workflow is theirs alone |
| contract verification against the real provider | not our job | keep Pact for contracts โ this page is about the dev-backend use case |
# query params that actually work โ no interaction variants written:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?sortBy=price&order=desc&limit=3'
# a real 404 with a JSON body where pact-stub-server sends 404-empty:
curl -i 'https://mockbird.mockbird.workers.dev/m/demo/products/999'
# the failure drills it has no flags for:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products/1?mock_delay=2000'
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_seq=500,200&mock_seq_key=me2'
# โ 500 first call, 200 after โ point your retry logic at it
| pact-stub-server | Mockbird | |
|---|---|---|
| Price | free, open source (MIT) | free while in beta |
| Purpose-built for | replaying Pact contract files | being a hosted mock backend |
| Hosted URL others can hit | no โ localhost Rust binary / Docker you run | yes โ 10,000 requests/project/day, no signup |
| Unknown path behavior | 404, empty body (verified v0.7.1) | 404 with a JSON error |
| Query params / new IDs | 404 unless in a pact interaction (or matcher) | always work |
| Writes persist | no โ canned responses only | yes โ POST โ GET it back |
| Latency / error / chaos simulation | none (full flag list catalogued) | mock_delay / mock_status / mock_chaos / mock_seq / mock_ratelimit |
| Scenario switching | provider states (strong) | snapshots + per-request pinning |
| Pact Broker integration | excellent โ its home turf | none |
| Works offline | yes | no |
| Maintenance | active (v0.7.1, June 2026) | actively developed |
Written by the Mockbird maker โ bias disclosed. Where pact-stub-server genuinely wins: it is the right tool for Pact contract workflows โ broker fetching with auth and filters, provider-state selection per request, watch-mode reloads, pact spec v4 โ and faithful exact replay is precisely what a contract stub should do. Keep Pact for contract testing; verification against the real provider is the whole point. Every behavioral claim above was verified by us in August 2026 against the v0.7.1 release binary (two-interaction v2 pact on :9123, requests as described); release date and maintenance status are from the pact-foundation/pact-stub-server GitHub releases and repo. If any of this changes, we'll update the page.
Full API reference in the docs. More guides: mountebank alternative ยท MockServer alternative ยท Hoverfly alternative ยท WireMock Cloud alternative ยท Prism alternative ยท Karate mock server alternative ยท testing loading & error states ยท free mock API tools compared. Create your API โ