Measure the timestamp before you interpret it
A timestamp field carries three facts: its unit, its epoch and the offset between the device clock and the server clock. Read one value from a capture and compare it with the device time recorded at that moment. A ten-digit number that tracks the clock in seconds, a thirteen-digit number in milliseconds, and an ISO 8601 string are all common. When the magnitude is wrong by a factor of a thousand, the client sends a value the server reads as decades away, and every request fails for a reason that has nothing to do with cryptography.
To find the offset, capture several requests over a few minutes and fit each value against the clock. A constant difference is source clock skew. A difference that grows means the app uses a different time source, such as a monotonic timer, or that the value is generated elsewhere and cached.
Establish whether the timestamp is signed
Whether the timestamp sits inside the signed material decides the client you have to build.
- Replace the timestamp with a current value and leave the signature alone. If the server refuses, the timestamp feeds the signature and your client must generate both together. If the server accepts, the timestamp is validated on its own and the signature covers something else.
- Replace the timestamp inside the signed string when you can compute the signature yourself. Changing the signed value tells you the field position and the format the server expects, because a wrong format and a wrong position fail differently.
- Set the timestamp far outside any plausible window. A distinctive error or a different status code confirms that a freshness check exists and that this field is the one it reads.
Rebuild the nonce
Collect twenty nonces from working requests and describe them. Note the length, the character set, and whether they contain a UUID shape, a base64 blob or a decimal counter. Then test the generation rule by sending a nonce your client produces in the same shape.
- The server accepts your own random value. It checks the shape or the uniqueness within a window, and your client can generate nonces freely.
- The server refuses a value it did not issue. The nonce comes from an earlier call, so your flow has to read it from that response and carry it forward.
- The server accepts a repeated value for a while and then refuses it. It keeps a replay cache, and the client has to stay ahead of the eviction window rather than merely produce unique values.
Test the tracking scope next. Send the same nonce from two sessions and observe which request fails. A per-session cache accepts the second session and a global cache refuses it. That difference decides whether parallel clients can share one nonce source.
Bound the acceptance window
Once the signature is correct, the window is measurable. Send requests with a timestamp offset that increases in steps, keeping every other field valid: zero, thirty seconds, a minute, five minutes, an hour. The largest offset that still passes is a lower bound on the window, and the first failure gives an upper bound. Run the test in both directions because some servers accept future timestamps more generously than past ones.
Use the measurement rather than a guess. A client that stamps requests with a stale cached time passes a local test and fails in production once the clock has drifted.
Rebuild the order inside the client
When the timestamp and nonce are both signed, generation order matters. The client reads or creates the nonce, stamps the time, builds the canonical string, computes the signature, and only then assembles the headers. Any step that runs late produces a signature over different bytes than the ones sent. Write the sequence down as one function that returns the full header set, and call it once per request rather than caching its output.
The clock belongs to the same function. If the app syncs time from a server response, the client must do the same, and a hard-coded offset holds only until the next drift. Android request signing covers the key and canonical string work that sits alongside these values, and a Python client for the app is where the rebuilt scheme usually ends up.
Related work
Reviewed 28 September 2026 · SReverse research desk