Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Troubleshooting14 min read

JSON-RPC -32600 Invalid Request: Envelope Validation

Validate the JSON-RPC 2.0 request envelope before it leaves your process so -32600 Invalid Request becomes a code-time impossibility, not a production incident.

TL;DR

JSON-RPC -32600 Invalid Request means the server parsed your body as JSON but the resulting value did not satisfy the JSON-RPC 2.0 request object contract. The specification defines a strict validation order: the body must parse at all (otherwise -32700 Parse error), the parsed value must be an Object or a non-empty Array (otherwise -32600), the envelope members must then satisfy Section 4 (otherwise -32600), and only after that are params examined (otherwise -32602 Invalid params). Most production -32600 incidents come from a truncated or concatenated body that still parses, a producer emitting jsonrpc: "1.0" or omitting the member entirely, or a hand-rolled batch that sends an empty array or a non-object element. This article gives you a runnable Node.js envelope validator, a CI pre-flight checklist, and a reproducible results table so you can prove the behaviour against your own endpoint rather than trusting a blog post.

The -32600 Invalid Request code in the JSON-RPC 2.0 specification

The JSON-RPC 2.0 specification defines -32600 Invalid Request in Section 5.1.1 as one of five pre-defined error codes, alongside -32700 Parse error, -32601 Method not found, -32602 Invalid params, and -32603 Internal error. The code is reserved for the case where the server received a value that parsed as JSON but is not a valid Request object. It is not a transport error, not an authentication error, and not a method-level failure — it is a statement about the shape of the envelope you sent.

Section 4 of the same specification defines the Request object: a member jsonrpc that MUST be exactly the string "2.0", a member method that MUST be a String, an optional member params that MUST be a Structured value (an Array or an Object), and an optional member id that MUST be a String, a Number, or Null. A server that validates strictly will reject any deviation from those constraints with -32600 before it ever looks at whether the method exists or whether the arguments are correct.

The Ethereum JSON-RPC specification layers a concrete method surface (eth_call, eth_getBalance, eth_sendRawTransaction, and so on) on top of that envelope, but it does not change the envelope rules. The same jsonrpc, method, params, and id contract applies to every Ethereum node endpoint, which is why a validator written once against the base specification works across providers. For a broader orientation to Ethereum endpoints, see the Ethereum network page.

  • Authoritative source: JSON-RPC 2.0 specification, Section 4 (Request object) and Section 5.1.1 (pre-defined error codes), https://www.jsonrpc.org/specification
  • Authoritative source: Ethereum JSON-RPC specification, https://ethereum.github.io/execution-apis/api-docs/
  • Documented behaviour: -32600 is returned when the parsed value is not a valid Request object; it is distinct from -32700 (body did not parse) and -32602 (envelope valid, params invalid).

Validation order: why the code you receive is a diagnosis, not a lottery

A conformant server does not evaluate every rule at once. It walks a fixed order, and the first rule that fails determines the code you see. That ordering is what turns the error code into a diagnosis: -32700 tells you the bytes never became JSON, -32600 tells you the JSON was not a valid envelope, and -32602 tells you the envelope was fine but the arguments were not.

The order is: first, the body must parse as JSON at all — if it does not, the server returns -32700 Parse error. Second, the parsed value must be an Object or a non-empty Array — if it is a bare string, number, boolean, or an empty array, the server returns -32600 Invalid Request. Third, the members of that object (or of each element of the array) must satisfy Section 4 — if jsonrpc is missing or not "2.0", if method is missing or not a string, if params is present but not an Array or Object, or if id is present but not a String, Number, or Null, the server returns -32600. Only fourth, once the envelope is valid, are params examined against the method signature, at which point a mismatch produces -32602.

This ordering matters because it tells you where to look. If you receive -32600, the problem is in your serialisation or your client library, not in your ABI encoding. If you receive -32602, the envelope is fine and the problem is in the arguments. The companion article on JSON-RPC -32602 invalid params validation covers that second stage in detail.

  • Body does not parse as JSON → -32700 Parse error
  • Parsed value is not an Object or non-empty Array → -32600 Invalid Request
  • Envelope members violate Section 4 → -32600 Invalid Request
  • Envelope valid but params do not match the method → -32602 Invalid params

A runnable Node.js envelope validator that predicts the server code

The most reliable way to make -32600 a code-time impossibility is to validate the envelope in your own process before the request is serialised and sent. The validator below returns the exact spec code the server would have returned, so a failing test tells you which rule you broke rather than leaving you to guess from a production log.

The function checks jsonrpc === "2.0" with no leading or trailing whitespace and no version drift such as "2" or "1.0", requires method to be a non-empty string, accepts params only as an Array or a plain Object (or absent), and accepts id only as a string, number, or null. Fractional numbers are flagged as a warning rather than a hard failure, because the specification discourages them but does not forbid them. Run it in a unit test against every request your application can construct.

// envelope-validator.js — predicts the JSON-RPC 2.0 error code for a request envelope
function validateEnvelope(value) {
  // Rule 1: parsed value must be an Object or a non-empty Array
  if (Array.isArray(value)) {
    if (value.length === 0) {
      return { code: -32600, message: 'Invalid Request', reason: 'empty batch array' };
    }
    return value.map((el, i) => ({ index: i, ...validateEnvelope(el) }));
  }
  if (value === null || typeof value !== 'object') {
    return { code: -32600, message: 'Invalid Request', reason: 'not an object' };
  }

  // Rule 2: jsonrpc must be exactly the string "2.0"
  if (value.jsonrpc !== '2.0') {
    return { code: -32600, message: 'Invalid Request', reason: 'jsonrpc must be exactly "2.0"' };
  }

  // Rule 3: method must be a non-empty string
  if (typeof value.method !== 'string' || value.method.length === 0) {
    return { code: -32600, message: 'Invalid Request', reason: 'method must be a non-empty string' };
  }

  // Rule 4: params, if present, must be an Array or a plain Object
  if ('params' in value) {
    const p = value.params;
    const isPlainObject = p !== null && typeof p === 'object' && !Array.isArray(p);
    if (!Array.isArray(p) && !isPlainObject) {
      return { code: -32600, message: 'Invalid Request', reason: 'params must be an Array or Object' };
    }
  }

  // Rule 5: id, if present, must be a String, Number, or Null
  if ('id' in value) {
    const id = value.id;
    const ok = id === null || typeof id === 'string' || typeof id === 'number';
    if (!ok) {
      return { code: -32600, message: 'Invalid Request', reason: 'id must be String, Number, or Null' };
    }
    if (typeof id === 'number' && !Number.isInteger(id)) {
      return { code: -32600, message: 'Invalid Request', reason: 'fractional id is discouraged', warning: true };
    }
  }

  return { code: 0, message: 'valid envelope' };
}

module.exports = { validateEnvelope };

// Example usage in a test:
// const { validateEnvelope } = require('./envelope-validator');
// const result = validateEnvelope({ jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: 1 });
// console.assert(result.code === 0, result);

The two most common production causes of -32600

The first cause is a body that still parses as JSON but is not a single object. Two JSON objects written back to back — for example {"jsonrpc":"2.0",...}{...} — will often be accepted by a lenient parser that reads the first value and ignores the trailing bytes, or rejected outright depending on the parser. A body wrapped in a newline-delimited stream, where the client concatenates several requests into one HTTP body, produces the same class of failure. The server sees a value that is not a single Request object and returns -32600.

The second cause is a producer that emits a version such as "1.0" or omits the jsonrpc member entirely, while the client library hides the envelope from the developer. This happens most often when a hand-rolled HTTP client, a proxy, or a middleware layer constructs the body itself and the application code only supplies method and params. The fix is to assert the envelope at the boundary where it is constructed, not where it is consumed.

Both causes share a signature: the request looks correct in application code, the error appears only against strict servers, and the same payload works against a lenient one. That asymmetry is exactly why envelope validation belongs in CI rather than in a post-incident review. The JSON-RPC error object decode guide explains how to read the code, message, and data fields once the error arrives.

  • Concatenated or newline-delimited bodies that parse but are not a single object
  • Producers emitting jsonrpc: "1.0" or omitting the member entirely
  • Middleware or proxies that rewrite the body without re-validating the envelope
  • Client libraries that hide the envelope and expose only method and params

How batch requests change the failure surface

A batch request is an Array of Request objects, and the specification treats an empty array as an Invalid Request in its own right: the server returns a single error object with code -32600 and id null. That is a documented behaviour, not a provider quirk, and it catches teams that build batches dynamically and occasionally produce an empty list.

For a non-empty batch, the specification requires the server to process each element independently. A single-element array that is not a valid Request object produces an Invalid Request entry for that element rather than failing the whole batch. This is the class of bug that makes hand-rolled batching unreliable: one malformed element silently corrupts the response array, and correlating the error back to the offending request requires the id field to be present and well-typed. The companion article on JSON-RPC id correlation and batch ordering covers how to map responses back to requests when some elements fail.

The practical rule is to validate every element of a batch with the same validator you use for a single request, and to reject an empty batch before it is serialised. If your batch builder can produce an empty array, it can produce a -32600, and the error will arrive with id null, which makes it hard to attribute to a specific caller.

  • Empty array [] → single -32600 response with id null
  • Non-empty array → each element validated independently
  • One invalid element → one Invalid Request entry, not a whole-batch failure
  • Validate every element with the same envelope validator used for single requests

Why the id type is part of the envelope contract

The id member is optional, but when present it MUST be a String, a Number, or Null. A null id is valid only for responses and for the requests whose id could not be detected — for example, a request that failed envelope validation before the server could read its id. A client that mints object ids, such as { id: { requestId: 1 } }, creates an Invalid Request on a strict server even though the rest of the envelope is correct.

Fractional numbers are explicitly discouraged by the specification. A server may accept id: 1.5 or may reject it; the specification does not guarantee either behaviour, so relying on it is a portability hazard. Use integers or strings for ids, and keep them unique within a batch so that responses can be correlated reliably.

The id type also affects how you read the error. When a server returns -32600 for a request whose id it could not detect, the error response carries id null. When it returns -32600 for a request whose id was readable but whose envelope was otherwise invalid, the error response may echo that id. That distinction is useful when you are triaging a batch failure and need to know whether the server could attribute the error at all.

  • Valid id types: String, Number, Null
  • Object ids are invalid and produce -32600 on strict servers
  • Fractional numbers are discouraged and may be rejected
  • id null in an error response often means the server could not detect the request id

Normalisation and pre-flight checklist for CI

A pre-flight checklist turns envelope validation from a debugging exercise into a build-time gate. Add the following checks to your test suite so that a malformed envelope never reaches the network. Each check maps to a rule in the specification, so a failure tells you exactly which rule you broke.

The checklist is deliberately small. It covers the members the specification validates, the batch cases that are easy to get wrong, and the serialisation boundary where concatenation bugs appear. If your application constructs requests in more than one place, run the checklist against every construction site, not just the primary one.

  • Assert jsonrpc === "2.0" with no leading or trailing whitespace
  • Assert method is a non-empty string
  • Assert params is absent, an Array, or a plain Object
  • Assert id is absent, a String, an integer Number, or Null
  • Assert a batch is a non-empty Array and every element passes the same checks
  • Assert the serialised body contains exactly one JSON value, with no trailing bytes
  • Assert the Content-Type header is application/json so the server parses the body as JSON
  • Run the validator in a unit test against every request your application can construct

Reproducible measurement: a results table to fill against your own endpoint

Documented behaviour tells you what the specification requires; it does not tell you what your specific provider returns. To get reproducible evidence, replay one deliberately broken envelope per row against your own endpoint and record the server code, the server message, the HTTP status, and the elapsed time. The table below is a template — fill it in with your own measurements rather than trusting numbers from any article, including this one.

Send each variant as a raw HTTP POST with Content-Type: application/json, and capture the full response body. Some providers answer HTTP 400 with a body that is not a JSON-RPC error object at all, which is why the HTTP status column matters as much as the code column. If you are comparing providers, run the same table against each endpoint and keep the raw responses alongside the table. The RPC endpoints guide explains how to obtain and configure endpoints for this kind of comparison.

  • Broken variant: jsonrpc: "1.0" — record server code, message, HTTP status, elapsed ms
  • Broken variant: jsonrpc member omitted — record server code, message, HTTP status, elapsed ms
  • Broken variant: method is a number — record server code, message, HTTP status, elapsed ms
  • Broken variant: params is a string — record server code, message, HTTP status, elapsed ms
  • Broken variant: id is an object — record server code, message, HTTP status, elapsed ms
  • Broken variant: two JSON objects concatenated — record server code, message, HTTP status, elapsed ms
  • Broken variant: empty batch array [] — record server code, message, HTTP status, elapsed ms
  • Broken variant: batch with one invalid element — record server code, message, HTTP status, elapsed ms
| Input variant | Observed code | Observed message | HTTP status | Elapsed ms |
| --- | --- | --- | --- | --- |
| jsonrpc: "1.0" | ____ | ____ | ____ | ____ |
| jsonrpc omitted | ____ | ____ | ____ | ____ |
| method is a number | ____ | ____ | ____ | ____ |
| params is a string | ____ | ____ | ____ | ____ |

Limitations, provider variation, and transport-level validation

The specification reserves the range -32000 to -32768 for implementation-defined errors, so a provider MAY return a non-standard code for the same condition. A server that returns -32000 for a malformed envelope is not violating the specification; it is using the implementation-defined range. This means your error handling must not assume that -32600 is the only code a malformed envelope can produce. Treat the code as a strong signal, not a guarantee.

Some providers answer HTTP 400 with a body that is not a JSON-RPC error object at all — for example, an HTML error page or a plain-text message from a load balancer. That is a transport-level failure, not an envelope-level one, and it must be handled separately. Your client should check the HTTP status and the Content-Type before attempting to parse the body as a JSON-RPC response, and should surface a distinct error class when the body is not a JSON-RPC error object.

Provider-specific behaviour varies. The specification defines the envelope contract, but it does not mandate a particular HTTP status, a particular message string, or a particular treatment of edge cases such as fractional ids. Where this article describes provider behaviour, treat it as documented or varies by provider, and verify against your own endpoint using the results table above. For a broader discussion of how to choose between providers, see the RPC pricing and API service pages.

  • The -32000 to -32768 range is reserved for implementation-defined errors; a provider may use it for the same condition
  • Some providers return HTTP 400 with a non-JSON-RPC body; handle transport errors separately
  • Message strings and HTTP statuses vary by provider and are not specified
  • Verify provider behaviour against your own endpoint rather than assuming a single code

Troubleshooting workflow for a -32600 in production

When a -32600 appears in production, the fastest path to a fix is to capture the raw request body and replay it against the validator. If the validator returns -32600, the bug is in your serialisation or your client library. If the validator returns 0, the bug is in the transport layer — a proxy, a middleware, or a load balancer that rewrote the body — and you should inspect the bytes on the wire rather than the object in your application code.

If the validator returns -32700, the body never parsed as JSON, which usually means truncation or a Content-Type mismatch. If it returns -32602, the envelope is fine and the problem is in the arguments, which is a different investigation. The companion article on decoding Ethereum revert reasons and custom errors covers the method-level failures that sit behind a valid envelope.

For batch failures, check whether the error response carries id null. If it does, the server could not attribute the error to a specific request, which usually means the batch itself was malformed — an empty array, or a body that was not an array at all. If the error response echoes an id, the batch was readable and one element was invalid; validate each element individually to find it.

  • Capture the raw request body, not the application object
  • Replay the body against the validator to localise the failure
  • Validator returns -32600 → serialisation or client library bug
  • Validator returns -32700 → truncation or Content-Type mismatch
  • Validator returns -32602 → envelope is fine, arguments are wrong
  • Error response carries id null → the batch itself was malformed

Next steps: making envelope validation part of the build

The goal is to move -32600 from a production incident to a failing unit test. Add the validator from this article to your test suite, run it against every request your application can construct, and fail the build when it returns a non-zero code. Add the results table to your provider evaluation checklist so that you have reproducible evidence of how each endpoint behaves before you depend on it.

If you are building against Ethereum, start from the Ethereum network page to confirm the method surface, and use the OnFinality Learn hub to find the companion articles on error objects, params validation, and id correlation. For endpoint configuration and provider selection, the RPC endpoints guide and the RPC pricing page cover the operational side. The API service page describes the managed endpoint offering if you would rather not run your own nodes.

Envelope validation is a small amount of code with a large payoff: it eliminates an entire class of production errors, it makes the remaining errors easier to diagnose, and it gives you a reproducible way to compare providers. The specification is short and stable, so the validator you write today will still be correct when the method surface grows.

  • Add the validator to your unit test suite and fail the build on a non-zero code
  • Add the results table to your provider evaluation checklist
  • Re-run the table whenever you change providers or upgrade a client library
  • Keep the raw request body in your logs so production errors can be replayed

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started