Skip to content

Connection security

Exchange API breaking changes: build contract tests

A new field and a new status require different tests. Seven instrument fixtures show how to preserve compatibility while blocking unknown values.

Ivory notched cards and lime tabs sit beside and inside a precision inspection jig on a black surface.
Illustration for connection security; examples in this article are educational.

Exchange API breaking change contract tests check whether the requests your connector sends and the responses it consumes still preserve the behavior your application depends on. Turn each relevant changelog entry into a versioned fixture and an explicit expected result. Allow documented additive response fields where they do not affect your consumer, but test changed types, missing required values and unfamiliar statuses separately. A passing replay establishes compatibility for those cases; it does not verify the exchange's live behavior.

A connector can break even when its endpoint still returns JSON. A strict decoder may reject a harmless extra property. A permissive decoder may accept a new status and incorrectly classify the instrument as tradable. The useful contract sits between those extremes: preserve the fields and meanings you need, and make uncertainty visible before it reaches an action.

Define the contract your connector actually consumes

Start with an inventory of requests, responses and decisions. Record the venue, API family, product category, environment, method, path and connector release. Include the SDK version if an SDK transforms the exchange's response before your code sees it.

For each interaction, identify the request parameters you send, required response fields, wire types, units, identity scope and documented error outcomes. Then name the downstream dependency: displaying a balance, deciding instrument eligibility, looking up an order or interpreting an acknowledgement. A field can be irrelevant to one consumer and essential to another.

Do not use a successful HTTP status as the entire contract. The exchange error-envelope guide explains why parsing, application acceptance and order completion require separate evidence. Your compatibility fixtures must reach the same interpretation boundary as the production connector.

This task also differs from an application changing its own AI message format. The AI schema migration guide addresses controlled producer and consumer versions. With an external exchange, you may neither control the rollout nor have access to provider-side verification.

Convert changelog entries into reviewable deltas

Read the changelog together with the current endpoint reference. Keep the publication date separate from any stated effective date, and record the affected product and account scope. An entry marked “update” does not tell you whether your particular connector will break.

Three dated entries in Bybit's V5 changelog illustrate different review questions:

Documented changeAffected interfaceFixture question for a consumer
September 29, 2026: add tags, an array of symbol tagsInstruments Info, linear and inverseDoes an extra property preserve the existing normalized result? Does policy need the new information?
September 1, 2026: add futures status PendingOpenInstruments Info and Account Instruments InfoIs this recognized as unavailable for order placement rather than treated as a generic active status?
August 6, 2026: change smpGroup from integer to stringOrder history, open orders and the order streamDoes decoding or comparison still depend on a numeric value?

These are examples from one venue, not a universal compatibility policy. The same release can require fixtures for REST and WebSocket consumers if both expose the changed field. Updating only the shared type declaration may leave a separate stream decoder unchanged.

Classify the delta before changing code: request acceptance, response structure, value meaning, routing or operational limits. A parameter becoming required needs a minimal-request test. A changed default needs an omitted-parameter test. A removed endpoint needs a routing decision, not another response fixture.

Routing belongs in this inventory too. A documented regional access change can affect which service receives the request even when the payload is unchanged; the regional endpoint guide covers that boundary. Keep unrelated endpoints outside the review scope, with a recorded reason rather than an assumed global exemption.

Accept extra fields without accepting extra permission

Avoid comparing an entire response byte for byte as the only test. Timestamps, generated identities and optional metadata can change while the consumer's required interpretation remains stable. Assert the relevant field types and normalized values, and assert exact values where the decision depends on them.

Pact's consumer-test guidance recommends assertions based on actual consumer dependencies and choosing response matching field by field. Its plugin matching contract separately describes allowing unexpected response keys. Those are testing design principles; they do not make arbitrary new exchange values safe.

Separate an unknown property from an unknown value of a known property. An unused futureMetadata field can be preserved or ignored under an explicit policy. An unfamiliar status occupies the field that decides eligibility. It must not fall through to “Trading” just because it is a nonempty string.

Likewise, permissive object decoding must still reject a missing required status, the wrong type or an identity mismatch. If a new field carries a restriction your application ought to honor, ignoring it may preserve parsing while leaving policy incomplete. Changelog review has to resolve that question; a schema matcher cannot.

Test units as well as types. A timestamp can remain an integer while its scale changes. The timestamp-unit guide explains how to make that dependency explicit instead of relying on the apparent size of the value.

Worked example: a field addition and a new instrument status

Consider a hypothetical connector that consumes linear instrument information. Its existing eligibility rule requires the exact status Trading; other checks independently cover freshness, instrument identity, permissions and order constraints. The example below tests only the status boundary and additive-field handling. It is not a complete trading gate or an observed exchange response.

Bybit's current Instruments Info reference documents status as a string, tags as an array of strings for linear and inverse products, and default results that include PendingOpen for linear and inverse queries. The changelog says orders cannot be placed in that state until it becomes Trading. Therefore, appearing in an instrument list alone is insufficient for this connector's eligibility rule.

Start with a deliberately reduced instrument fragment: symbol is the invented string EXAMPLEUSDT, and status is Trading. A full endpoint test would also validate the surrounding response and required instrument specifications.

Preserve that baseline, then make one change per fixture. For this exercise, the consumer has no tag-based policy; the reviewer must explicitly confirm that assumption before accepting the additive-field result.

FixtureChange from the baselineRequired interpretationEligible at this boundary?
ANoneValid, recognized trading statusYes, subject to all other checks
BAdd tags: []Same required interpretation as AYes, under the reviewed tag policy
CSet status to PendingOpenRecognized, currently unavailableNo
DSet status to invented FutureStateUnknown status, preserve its raw valueNo
ERemove statusRequired-field failureNo
FSet status to numeric 0Type failureNo
GSet status to nullType failure under this contractNo

There are two eligible classifications and five blocked classifications in these seven constructed cases. Those counts describe fixture outcomes, not exchange reliability or trading results. The critical assertions are that B retains A's required interpretation and that C through G cannot reach the mocked submission boundary.

A strict object validator that rejects B has an additive-field problem. A rule such as status !== "Closed" would incorrectly pass C and D; that is a policy problem. Replacing every unfamiliar status with Trading to avoid a decoder error makes the second problem harder to detect.

Keep the distinctions in the normalized result: recognized_unavailable, unknown_status and contract_failure should remain distinguishable. When a refresh becomes unusable, mark the current eligibility unresolved or blocked. A previously cached Trading observation may remain useful for diagnostics, but it must not silently supply current eligibility.

Replay the real decoder, then check the live boundary

Run the fixtures through the released decoder and the proposed decoder. Preserve the old cases so fixing the new response does not quietly remove support for documented inputs you still need. If the SDK performs decoding, exercise that layer too; a handcrafted test object can miss a transformation that occurs before your adapter runs.

Use a mocked action boundary for negative cases and record that it receives no submission call. Keep broader eligibility-policy tests alongside the contract tests: parsing a known status and authorizing an order are separate responsibilities. Do not make a contract test assert the exchange's matching-engine behavior.

An offline replay only shows how your code handles the supplied messages. Label the evidence explicitly: local decoder replay, testnet observation or production read-only observation. Claim provider-side verification only when that process actually occurred. Calling a local fixture suite “exchange certified” would overstate what happened.

After replay, compare current documented behavior with permitted read-only observations in the intended environment. Use testnet where appropriate for write-path checks, while recording differences from production. A successful response from one environment does not establish that a not-yet-observed branch or account mode behaves identically elsewhere.

Make the release gate an evidence record

For each relevant changelog entry, retain its URL, reviewed date, effective-date information, affected interfaces, fixture names, decoder versions and expected outcomes. Attach fixture hashes and actual results. If you exclude an entry, record why the consumer has no dependency on it.

Block a release when a consumed field changes meaning without a reviewed mapping, a required input becomes unavailable, an unknown value grants eligibility, or an affected interface has no coverage. An additive field can pass when the required interpretation stays intact and policy review confirms that the field introduces no unmet dependency.

Never automatically regenerate expected outputs from a newly observed response and call the difference resolved. That can bless the very behavior the test was supposed to catch. Preserve the failing case, explain the intended interpretation, and review the changed expectation.

The next useful step is one concrete change record: choose a changelog entry for an endpoint you use, preserve the old fixture, construct the new and malformed cases, and state which outcomes must stop at the mocked action boundary. Release evidence becomes useful when someone can reproduce that decision from the retained inputs.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.