Start with the class of refusal
A 4xx response means the request arrived and the server evaluated it. Different codes point at different checks, and that mapping narrows the search before you open a decompiler.
- 400 usually means the request could not be parsed or a required field is malformed, which points at serialisation rather than signing.
- 401 means the credential or session was missing, expired or not recognised, and the signed material is often fine.
- 403 means the request was understood and refused. Signature mismatch, an integrity check and a policy rule all land here, so the body matters more than the code.
- 404 can mean a wrong path and also a version or environment mismatch, especially when the app talks to a different base URL than the one you found.
- 409 points at server state that conflicts with the request, such as a resource that already exists or a workflow step out of order.
- 412 is a precondition failure and frequently carries a freshness check on a timestamp or a version counter.
- 422 means the body parsed and the values failed validation, which is a field problem rather than a signing problem.
- 429 means the request was rate limited or flagged as a repeat, and a nonce replay defence can produce it.
Map the server's error taxonomy
Error bodies are more informative than codes when the server distinguishes causes. Read the body before touching the client and look for an application code, a message and a request identifier that support can trace.
Where the server returns the same generic body for every failure, build the taxonomy yourself with deliberate breaks. Send a request with a corrupted signature header and record the response. Send one with no signature at all. Send one with a valid signature and a tampered body. Send one with an expired session token. Each test costs a minute and tells you which failure mode is in front of you, and the set of responses becomes the key you use for the rest of the investigation.
Compare against working traffic
A single failing request carries little information on its own. Put it beside a request that succeeded from the same app and compare the pairs: status, headers, body shape, and the values that change between calls. A field that differs in two working captures is generated per request, and a field that matches across working captures but differs in yours is where the failure sits.
Check the timing as well. If the working capture is old, a freshness window can explain the refusal without any defect in your reproduction, and the cheapest test is to obtain a new capture and replay it immediately.
Run the cheap tests in order
Order the experiments by cost and stop as soon as the response changes.
- Resend the identical request twice. Success followed by failure indicates a one-time value such as a nonce.
- Refresh only the timestamp. A change in outcome means freshness is enforced and the timestamp is signed or validated.
- Obtain a fresh session token and resend. A change in outcome moves the problem to authentication and token lifetime.
- Change one derived value at a time. Each distinct response tells you that the value is part of the signed material.
- Send the request from the original device. If it passes there and fails elsewhere, device-bound inputs are in play.
Failures that sit outside your client
Some 4xx responses are produced before the application sees the request. A content delivery network or web application firewall in front of the API can refuse a replay with a 403 and an HTML body that looks nothing like the API's error format. A missing integrity token from the platform attestation service produces a policy refusal that no header edit will fix. TLS or HTTP/2 fingerprinting can also distinguish a scripted client from the app, and the symptom is a refusal that does not vary when you change request content.
Look at the response headers and body format when a failure resists every content change. A server name or content type that differs from the API's normal responses is a strong signal that something in front of the application answered you.
Write down what the endpoint requires
Record one page per endpoint: the required headers, the values derived per request, the session dependency, and the outcome of each test. That page is the specification your client implements, and it is also what makes the next 4xx diagnosable in minutes instead of hours. Most of the value of the exercise is the record, because the same failure returns whenever a token expires or a scheme changes.
Why a request works in the app and fails in Python covers the general case, and 403 responses in Postman covers the replay tool specifically. Fixing 401 and 403 on replayed Android requests is the service that turns the triage into a client that passes.
Related work
Reviewed 28 September 2026 · SReverse research desk