What the protocol keeps and what it replaces
The request method, the path, the query string, the header values and the body bytes all cross the wire. What disappears is the request line and the status line as text, because HTTP/2 has no text form. The client sends a connection preface, a settings frame, and then a sequence of frames on a stream. Each frame carries a type, a set of flags, a stream identifier and a payload up to the negotiated maximum size.
That has a practical consequence for a capture taken inside the app. If you hook the client after the request object is built but before the encoder runs, you see the request as the app represents it. If you hook lower, you see bytes. The two views are not interchangeable, and a capture that shows text without stating where it was taken tells you only half of what you need.
TLS adds a second problem. HTTP/2 is encrypted in almost every Android app, and a proxy that terminates TLS rebuilds the request from the framing it decoded. The server never received a request line, so any tool that prints one computed it from the pseudo-header block.
Header field encoding and pseudo-headers
Header names are lower case on the wire, and a request that carries an upper case name is rejected as a protocol error. That matters because servers often sign a canonical string built from headers. If the server builds that string from its own decoded, lower case view, a signature computed over an original mixed case name will not match.
HPACK compresses the header block against a table shared by both sides of the connection. The encoded field may be an index into that table or a literal with a length, and the table changes as a connection carries more requests. Two identical requests sent at different points in a session can therefore produce different header bytes on the wire, which is why a capture tool that attaches to raw frames shows values that no longer resemble names and strings.
Pseudo-headers replace the request line. The client sends the method, scheme, authority and path as fields beginning with a colon, and they have to come before the regular fields. A single header byte can be split across a continuation frame. When you reconstruct the request, the authority is the host the request is targeted at, and the path holds the query. Anything the client derived for a signature is usually built from those four values plus selected headers rather than from the framing.
Multiplexing changes the order you observe
HTTP/2 runs many requests at once over one connection, each on its own stream. The connection carries data frames from several streams interleaved, with flow control windows applying at both the stream and connection level. A request that appears as one tidy unit in a tool was, on the wire, a header frame followed by body frames that may have been mixed with frames from other streams.
Replay tools assume the request and response model that a single connection with sequential exchanges provides. When a call depends on another call completing first, that dependency exists in the application, not in the transport. A capture tool can show two requests arriving out of order and still be reporting the truth about frame order.
Response bodies present the same issue. Data frames from one stream arrive in order, but a tool that decodes each stream separately will print them in the order it finished decoding rather than the order the connection carried them.
Why the tool can show a request the server never saw
A request exists in three places. The app builds it in memory after the interceptor chain has run. The client encodes it into frames. The server decodes the frames and reconstructs the fields. A capture taken at the first point reflects a request that the encoder had not yet touched, at the second point shows bytes that determine what the server can read, and only at the third point is the answer to what the server received.
Tools that render a readable view are showing the decoded form. That view drops the difference between an absent field and a field sent with an empty value, hides the order chosen by the encoder, and rewrites the authority and path into a URL that the server never assembled in that shape.
Reconstructing the server's view means working from what the client encoded, not from what a tool printed. A client that reproduces an app's requests has to build the same field set at the same layer, and it has to decide whether a signature covers the value the app holds or the value that survives encoding.
If an endpoint refuses a request that looks correct in every viewer, compare the field set the app encoded with the field set your client sent. SReverse reproduces the encoded request in a Python client and reconciles it with the capture, so the difference between the two views stops being invisible. The transport layer that creates this problem is covered under APK to callable API.
Related work
Reviewed 28 September 2026 · SReverse research desk