When a webhook payload changes, a generic JSON diff can tell you that a value or path changed. It usually cannot tell you which change is likely to break a consumer, or give you a regression test for the code that consumes it.
This matters most when your code assumes things such as:
- a field always exists;
- a number will never become a string;
- a nested object will never become an array;
- a value cannot be
null; - every item in an array has the same shape.
Start with the consumer, not the provider
Suppose an old payload contains:
{
"amount": 1200,
"customer": {
"email": "dev@example.test"
}
}
The new sample contains:
{
"amount": "1200",
"customer": {}
}
A syntax diff reports two changes. A consumer-oriented review asks different questions:
- Does a decoder require
amountto be a number? - Does application code read
customer.emailwithout a fallback? - Can a sanitized version of the new payload be kept as a regression fixture?
- Can the actual consumer/parser be executed against that fixture in CI?
What two samples can and cannot prove
Two samples can reveal structural candidates:
- removed paths;
- scalar type changes;
- object/array/scalar shape changes;
- newly observed
nullvalues; - nested-path changes;
- array item shape changes.
They cannot prove the provider's complete contract. In particular, they do not establish every required or optional field, all enum values, conditional variants, or every possible array item.
That is why a useful scanner should say candidate, not confirmed breaking change.
Turn the comparison into a test
The most useful output is not another diff screen. It is a sanitized fixture and a test that you can adapt to call the real consumer:
describe('webhook contract drift', () => {
test('the consumer accepts the reviewed fixture', () => {
const result = consumeWebhook(newFixture)
expect(result).toMatchObject(expectedBusinessOutput)
})
})
The assertion should be about consumer behavior, not only JSON shape.
Keep production payloads out of unnecessary systems
Webhook payloads can contain customer or operational data. For a one-off comparison, there is no need to upload them to a scanning backend. A browser-local tool can compare the two inputs and generate the fixture locally.
You should still sanitize secrets and personal data before pasting them into any developer tool.
A small experimental tool
I built an experimental browser-local scanner around this workflow. It compares old and new JSON samples, explains consumer-risk candidates, and generates a Jest/Vitest fixture test.
It does not upload or store payload content, does not require an account, and does not claim to infer a complete provider contract.
Try the free scanner: https://nmasaya24884-byte.github.io/webhook-contract-drift-scanner/
Source and limitations: https://github.com/nmasaya24884-byte/webhook-contract-drift-scanner
The tool is an experiment. Feedback should focus on whether the consumer-impact explanation and generated test are useful—not on treating its output as a guarantee.
Disclosure: This article was drafted with AI assistance and reviewed against the tool's implementation, tests, limitations, and DEV Community content guidelines before publication.
Top comments (0)