Capture both sides at the same layer
Most false differences come from comparing two different layers. An app-side log written inside the networking library shows the request before or after some interceptors have run, and your script's log shows what it handed to its own library. Those two views can disagree about header order, transfer encoding and even the body bytes while both are correct.
Capture the bytes that reach the socket on both sides. On the app, log the final request after the whole interceptor chain, or read the wire with a decrypting proxy. On the script side, use the same kind of hook or proxy rather than the library's own request object. Then write both to files in the same format and diff them as text.
Filter the differences that do not matter
A raw diff of two clients is noisy, and a few fields always differ.
- Transport headers differ because the client library sets them. Host, Connection, Accept-Encoding and Content-Length are usually ignored by the server, so check one field at a time before treating any of them as the cause.
- Protocol version changes the shape of the whole message. An HTTP/2 request carries pseudo-headers and represents headers differently from an HTTP/1.1 request.
- Library identity appears in a default user agent. If the app sets its own and the script leaves the library default, the two differ in a header the server may log or validate.
- Connection reuse lets a second request drop headers the first one sent. Compare requests that sit in the same position in the flow so both sides are at the same point in the connection lifecycle.
Remove those from the diff and keep the rest. The remaining lines are candidates.
Compare the body byte for byte
Two JSON encoders rarely produce identical bytes for the same object. Key order, whitespace, the escaping of slashes and non-ASCII characters, and number formatting all differ between libraries. A server that signs or hashes the raw body sees those differences as a different message.
- Send the captured body bytes verbatim from your client. If the request then succeeds, the difference is in how your client serialises the object, and the fix is a custom encoder rather than a new header.
- Re-serialise the captured body with your client's encoder and diff the bytes. The first differing offset shows the field and the reason.
- Check for a trailing newline or a content type mismatch. A body sent as form data instead of JSON, or the reverse, changes both the bytes and the content type header.
Do not compare hashes of the two bodies. A hash tells you they differ and nothing about where.
Compare the path and query encoding
Query strings are a common source of byte differences. Encoders disagree about spaces, plus signs, the case of percent escapes, and the order of parameters. A server that signs the query string rejects a semantically identical query that was encoded differently.
Take the path and query from the capture and place them in the client as a literal string that is not re-encoded. If the request then succeeds, the encoding is the difference. If it still fails, the path and query are fine and the difference lies elsewhere. This test is quick and it removes a whole class of guesses.
Change one thing at a time
Start from the captured request and move one field at a time toward what your client sends. After each change, send the request and record the response. The change that flips the response from failure to success names the difference. Doing it in the other direction works as well: start from your client and move one field toward the capture.
Keep a written list of what you have changed, because a request with several edits at once tells you nothing when it fails. When the two requests are identical except for one derived value such as a signature or a timestamp, the diff has done its job and the remaining work is reproducing that value. Request signing covers that work, and 401 and 403 replay diagnosis covers the status codes the server returns while you narrow it down. A working Python client is the usual end state.
Related work
Reviewed 28 September 2026 · SReverse research desk