JSON-RPC -32603 is the specification's reserved code for an internal server fault, not a business-logic signal. Because any exception inside a node's method handler collapses into this single code, the same -32603 can mean malformed params, an unsupported argument combination, a node panic, or a provider routing failure. The reliable way to debug it is a three-layer bisection: send a raw curl request, replay the failing call as eth_call with explicit gas, then decode the returned data field. This article provides a runnable Node.js example, a results table you fill in against your own endpoints, and a checklist that separates client bugs from node and provider faults.
Specification Semantics of Error Code -32603
The JSON-RPC 2.0 specification defines a reserved error range from -32768 to -32000 and assigns -32603 the message "Internal error" (JSON-RPC 2.0 specification). The specification describes this as a server error: the request was received and parsed, but the server encountered an unexpected condition while processing it. It is deliberately coarse. The spec does not require a specific cause, only that the server could not complete the method call.
The Ethereum JSON-RPC specification builds on that base and documents method-level error behavior for calls such as eth_call and eth_estimateGas (Ethereum JSON-RPC specification). In practice, execution clients map many internal exceptions to -32603, including parameter decoding failures, unsupported argument combinations, and panics inside the method handler. That is why the code alone rarely identifies the fault.
For a broader orientation to endpoint behavior and provider selection, see the RPC endpoints guide and the OnFinality Learn hub.
- -32603 is a server error, not a client error, in the JSON-RPC 2.0 taxonomy.
- The message is fixed as "Internal error" but the cause is implementation-defined.
- The code is reserved by the specification and must not be reused for application-specific errors.
Classification Table: -32603 Versus Neighboring Error Codes
Distinguishing -32603 from adjacent codes is the first diagnostic step. -32602 (Invalid params) means the request structure was understood but the parameters failed validation. -32601 (Method not found) means the node does not expose that method. The -32000 range is reserved for implementation-defined server errors, and many execution clients use it for execution reverts and state-related failures.
The table below summarizes the documented distinction. Treat the -32000-range entries as documented behavior that varies by client and provider, because the specification leaves their exact meaning to the implementation.
- -32603 Internal error: server-side exception during method execution; cause not specified.
- -32602 Invalid params: parameter shape or type rejected before execution.
- -32601 Method not found: method name not supported by the node.
- -32000 range: implementation-defined; commonly used for execution reverts and state errors.
- -32603 is the least specific of these and therefore the hardest to branch on.
Why -32603 Is a Coarse Signal
Any exception raised inside a node's method handler can collapse into -32603. A malformed params object, an unsupported combination of arguments, a node running out of memory, or a provider's load balancer failing to route the request can all produce the same code. The specification does not require the server to expose the underlying exception, so the code is a symptom, not a diagnosis.
This coarseness is why forum threads about -32603 often contain contradictory fixes. One user's -32603 is a serialization bug in their client; another's is an upstream proxy returning an auth failure with the wrong code. The only reliable approach is to bisect the request path until the failing layer is isolated.
Three-Layer Bisection Method for Isolating the Fault
Bisection means removing layers until the error changes or disappears. Start with a raw curl request that bypasses your application library entirely. If the raw request succeeds, the fault is in your client code or its serialization. If it fails, the fault is in the node or the provider path.
Next, replay the failing call as eth_call with explicit from, to, value, and gas fields. Estimation failures are the most common -32603 trigger in production, and eth_call with explicit gas often returns a decodable revert payload instead of a generic internal error. Finally, decode the data field if one is present. A revert selector or custom error in data tells you the call reached the EVM and failed there, which points to contract logic rather than infrastructure.
For revert decoding specifics, see Decoding Ethereum revert reasons and custom errors.
- Layer 1: raw curl with a minimal, well-formed request.
- Layer 2: eth_call with explicit gas and full call fields.
- Layer 3: decode the data field for a revert selector or custom error.
- If the error changes between layers, the last layer you removed is implicated.
Runnable Node.js Example That Prints the Raw Error Object
Client libraries such as ethers and viem wrap -32603 and often hide the data payload. The example below issues a deliberately well-formed request using fetch and prints the raw JSON-RPC error object, including code, message, and data. Run it against your endpoint to see what the node actually returns before any library abstraction.
Replace the endpoint URL with your own. The request is intentionally minimal so that any failure is attributable to the node or provider, not to the request shape.
const endpoint = 'https://your-endpoint.example';
async function probe() {
const body = {
jsonrpc: '2.0',
id: 1,
method: 'eth_estimateGas',
params: [{
from: '0x0000000000000000000000000000000000000000',
to: '0x0000000000000000000000000000000000000000',
value: '0x0'
}]
};
const started = Date.now();
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
});
const elapsed = Date.now() - started;
const json = await res.json();
console.log('httpStatus', res.status);
console.log('elapsedMs', elapsed);
console.log('error', JSON.stringify(json.error, null, 2));
console.log('result', json.result);
}
probe().catch((e) => console.error('transport', e));eth_estimateGas and the -32603 Trigger Pattern
Estimation failures are the most common production source of -32603. When eth_estimateGas cannot determine a gas limit, the node may raise an internal exception that surfaces as -32603 rather than a structured revert. The fix is to replay the same call as eth_call with an explicit gas value, which forces the EVM to execute and return revert data instead of failing during estimation.
Use the same from, to, value, and data fields from the failed estimate. Set gas to a value high enough to avoid an out-of-gas during the replay, then decode the returned data. If the replay returns a revert selector, the fault is contract logic. If it returns -32603 again, the fault is more likely node or provider infrastructure.
For related transport-level failures, see How to fix RPC timeout errors and How to fix RPC 429 errors.
- Estimation failures frequently surface as -32603 instead of a revert.
- Replay as eth_call with explicit gas to force execution.
- Decode the data field to distinguish contract logic from infrastructure.
Verifying Provider-Side Faults by Replaying Against a Second Endpoint
If the raw request fails, replay the identical request against a second endpoint. If the second endpoint succeeds, the fault is provider-side or specific to that node. If both fail identically, the fault is likely in the request itself or in the contract call. Record the results in a table so the comparison is reproducible.
The table below is a template. Fill it in with your own measurements. Do not rely on published latency or error-rate figures from any provider, including OnFinality; measure against your own endpoints.
- Endpoint: the URL you tested.
- Code: the JSON-RPC error code returned.
- Message: the error message string.
- Data: any data payload returned.
- HTTP status: the transport-level status code.
- Elapsed ms: wall-clock time for the request.
Role of the data Field and MetaMask Behavior
The data field is optional in the JSON-RPC 2.0 error object, and its contents are implementation-defined. When a node includes a revert payload in data, you can decode it to identify the failing contract condition. When data is absent, the error is opaque and you must rely on bisection.
MetaMask and similar wallets often surface -32603 with no data because the wallet's internal provider wraps the node response and discards the payload. A direct node call may include a revert payload that the wallet hides. This is documented behavior that varies by wallet version and provider, so always confirm with a raw request before concluding the node returned nothing useful.
For endpoint selection guidance, see the RPC endpoints guide and RPC pricing.
Common Failure Modes and Their Fixes
Several recurring patterns produce -32603. Hex quantity serialization is a frequent culprit: values such as gas or value must be hex-encoded strings, not decimal numbers. A null result coerced into a thrown error by a client library is another: some libraries treat a null result as an error even when the node returned a valid response. An upstream proxy mis-surfacing a rate-limit or auth failure as -32603 is a third pattern, often visible only when comparing HTTP status codes across endpoints.
Batch requests add another dimension. In a batch, one element can carry the -32603 error while others succeed. Inspect each element's error object individually rather than treating the batch as a single failure. For batching specifics, see JSON-RPC batching best practices.
- Hex quantity serialization: ensure gas, value, and nonce are hex strings.
- Null result coercion: check whether your library throws on null results.
- Proxy mis-surfacing: compare HTTP status codes across endpoints.
- Batch element errors: inspect each element's error object separately.
Troubleshooting Checklist for -32603
Work through the checklist in order. Each step removes a layer of abstraction and narrows the fault domain. Stop when the error changes or disappears, because that identifies the layer you just removed.
If the checklist does not resolve the issue, escalate with the raw request, the raw response, the endpoint URL, and the results table. That evidence lets a provider or node operator reproduce the fault without guessing.
- Reproduce with raw curl, bypassing your application library.
- Verify all hex quantities are strings, not numbers.
- Replay as eth_call with explicit gas and full call fields.
- Decode the data field if present.
- Replay against a second endpoint and compare.
- Inspect batch elements individually.
- Check HTTP status codes for proxy-level failures.
- Record results in a table before escalating.
Limitations and Tradeoffs of Branching on -32603
-32603 is not stable enough to branch on in application logic. Because the same code can mean a client bug, a node panic, or a provider routing failure, treating it as a business-logic signal will produce incorrect behavior. The recommended pattern is to retry once, then surface the error for human debugging rather than attempting automated recovery.
This limitation is inherent to the specification's design. The reserved range exists to give servers a generic escape hatch, not to provide precise diagnostics. Applications that need precise error handling should rely on method-specific error data where available, and treat -32603 as an unknown fault.
For related error-handling patterns, see How to fix RPC 429 errors and How to fix RPC timeout errors.
- Do not branch on -32603 for business logic.
- Retry once, then surface for human debugging.
- Prefer method-specific error data when available.
- Treat -32603 as an unknown fault by default.
Next Steps for Reliable RPC Error Handling
After isolating a -32603 fault, the next step is to harden your request path. Validate hex serialization before sending, log raw responses alongside library-level errors, and maintain a second endpoint for comparison. These practices reduce the time to diagnose future internal errors.
For production workloads, consider a provider that exposes raw JSON-RPC responses without wrapping. Explore Ethereum endpoints, review API service options, and consult RPC pricing to match your error-handling requirements. The OnFinality Learn hub collects related troubleshooting guides.
- Validate hex serialization before sending requests.
- Log raw responses alongside library errors.
- Maintain a second endpoint for comparison.
- Choose a provider that exposes raw JSON-RPC responses.