The trade-off protobuf makes
A JSON body carries its own field names: {"userId": 42} tells you what the number means just by looking at it. Protocol Buffers deliberately don't do this. On the wire, that same value is encoded as a field number (not a name) plus a type tag plus the value — smaller and faster to parse than JSON, but meaningless without the .proto schema file that maps field number 1 back to the name user_id. That schema isn't sent with the message; both sides are expected to already have it, compiled into the app and the server ahead of time.
What a debugging tool sees without that file
Without the schema, a proxy can still parse the wire format structurally — protobuf's encoding is self-describing enough to tell you "field 3 is a length-delimited value" or "field 7 is a varint" even without knowing what those fields are called. That gets you a readable tree of field numbers and typed values instead of an opaque blob of bytes, which is usually enough to spot the field that changed between two requests, even without its name. It's a real step up from nothing, but it's not the same as having the actual schema — a field genuinely named user_id is more useful than a field labeled 3.
gRPC adds its own layer on top
gRPC itself is a specific way of using HTTP/2 to carry protobuf messages, with its own conventions worth knowing: it uses HTTP/2 trailers (headers sent after the body) to carry the actual success/failure status, so a gRPC call can show HTTP 200 at the header level while the trailer says the call actually failed — the same "don't trust the top-level status alone" trap that GraphQL has, for a similar reason. It also supports true bidirectional streaming, where either side can send multiple messages over one call, closer to a WebSocket's shape than a single REST request/response.
A practical approach when you don't have the .proto file
- Read the structural tree a decoder produces rather than raw bytes — field numbers and types, even unlabeled, are far more scannable than binary.
- Compare two similar requests side by side. A field that changes value in a predictable way between them is usually identifiable from context, even without its name.
- Check the gRPC trailer, not just the HTTP status, when a call looks like it succeeded but the app behaved as if it failed.
- If you do have access to the .proto file, use it — a schema-aware decoder will always beat a structural guess.
Hollowport decodes gRPC and raw Protocol Buffers automatically, even with no schema to work from.