Migrating from QuickNode to NOWNodes
This tutorial shows how to move from QuickNode to NOWNodes without turning the migration into a full rewrite. Standard RPC requests usually need only a new endpoint and a new authentication method. QuickNode-specific products, such as Streams, Webhooks, Marketplace Add-ons, IPFS, KV storage, and Solana gRPC, should be migrated separately.
1. Start with a Quick Audit
Before changing code, list what your app currently uses in QuickNode.
Check for:
- HTTP RPC endpoints, usually ending with
quiknode.pro/{token}/; - WSS endpoints;
- standard methods such as
eth_blockNumber,eth_getBalance,eth_call,eth_getLogs,eth_sendRawTransaction; - debug or trace methods such as
debug_traceTransaction; - QuickNode Streams;
- QuickNode Webhooks;
- Marketplace Add-ons;
- REST APIs that use
x-api-key; - Solana gRPC, IPFS, or KV storage;
- current request rate, errors, and alerts.
Search your codebase for:
quiknode.pro
QUICKNODE
x-api-key
@quicknode/sdk
debug_trace
trace_The main goal of this audit is to separate standard RPC usage from QuickNode-specific features.
2. What Moves Directly
These standard RPC methods can usually be sent to NOWNodes without changing the JSON-RPC body:
| Method | Migration |
|---|---|
eth_chainId | Direct |
eth_blockNumber | Direct |
eth_getBalance | Direct |
eth_call | Direct |
eth_estimateGas | Direct |
eth_getLogs | Direct, but check block ranges |
eth_getTransactionReceipt | Direct |
eth_sendRawTransaction | Direct, but test nonce and gas behavior |
debug_traceTransaction | Check NOWNodes Trace & Debug support first |
QuickNode often puts the token in the URL:
https://your-endpoint.quiknode.pro/YOUR_QUICKNODE_TOKEN/NOWNodes uses a network endpoint plus the api-key header:
https://eth.nownodes.io/
api-key: YOUR_NOWNODES_API_KEY3. What Does Not Move Directly
Do not treat these as simple URL replacements:
| QuickNode feature | What to do instead |
|---|---|
| Streams | Replace with WSS + worker, eth_getLogs, Blockbook, or your own indexer |
| Webhooks | Use NOWNodes Webhooks if available, or build a small notification worker |
| Marketplace Add-ons | Check each add-on separately and replace its custom API/methods |
REST APIs with x-api-key | Replace with the matching NOWNodes API or another service |
| IPFS | Move to an IPFS provider or self-hosted IPFS |
| KV storage | Move to your app database or managed KV |
| Solana gRPC | Keep temporarily or move to a compatible gRPC provider |
Move standard RPC first. Migrate these product-specific features after the core provider switch is stable.
4. Endpoint Mapping
QuickNode endpoint names are custom. Use your actual dashboard URL as the source and switch to the matching NOWNodes network endpoint.
| Network | QuickNode pattern | NOWNodes HTTP | NOWNodes WSS |
|---|---|---|---|
| Ethereum Mainnet | https://{endpoint}.quiknode.pro/{token}/ | https://eth.nownodes.io/ | wss://eth.nownodes.io/wss/{NOWNODES_API_KEY} |
| Ethereum Sepolia | QuickNode dashboard URL | https://eth-sepolia.nownodes.io/ | Check NOWNodes docs |
| Polygon Mainnet | QuickNode dashboard URL | https://matic.nownodes.io/ | wss://matic.nownodes.io/wss/{NOWNODES_API_KEY} |
| Base Mainnet | QuickNode dashboard URL | https://base.nownodes.io/ | wss://base.nownodes.io/wss/{NOWNODES_API_KEY} |
| Arbitrum One | QuickNode dashboard URL | https://arbitrum.nownodes.io/ | Check NOWNodes docs |
| Optimism Mainnet | QuickNode dashboard URL | https://optimism.nownodes.io/ | wss://optimism.nownodes.io/wss/{NOWNODES_API_KEY} |
| BNB Smart Chain | QuickNode dashboard URL | https://bsc.nownodes.io/ | wss://bsc.nownodes.io/wss/{NOWNODES_API_KEY} |
| Solana Mainnet | QuickNode dashboard URL | https://sol.nownodes.io/ | wss://sol.nownodes.io/wss/{NOWNODES_API_KEY} |
Always verify the network with eth_chainId or the chain-specific equivalent before sending real traffic.
5. Configure Environment Variables
Before:
QUICKNODE_ETH_RPC_URL=https://your-endpoint.quiknode.pro/YOUR_QUICKNODE_TOKEN/
QUICKNODE_ETH_WSS_URL=wss://your-endpoint.quiknode.pro/YOUR_QUICKNODE_TOKEN/After:
NOWNODES_ETH_RPC_URL=https://eth.nownodes.io/
NOWNODES_ETH_WSS_URL=wss://eth.nownodes.io/wss/YOUR_NOWNODES_API_KEY
NOWNODES_API_KEY=YOUR_NOWNODES_API_KEYDuring rollout, keep both providers available:
RPC_PROVIDER=nownodes
QUICKNODE_ETH_RPC_URL=https://your-endpoint.quiknode.pro/YOUR_QUICKNODE_TOKEN/
NOWNODES_ETH_RPC_URL=https://eth.nownodes.io/
NOWNODES_API_KEY=YOUR_NOWNODES_API_KEYThis makes rollback a configuration change instead of another code change.
6. Test the New RPC Endpoint
First, check the current QuickNode response.
curl "https://your-endpoint.quiknode.pro/$QUICKNODE_TOKEN/" \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'Then run the same request through NOWNodes.
curl "https://eth.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'Check:
- both responses return
result; - NOWNodes is close to QuickNode by block number;
- no
401,403,429, or5xxerrors appear; - request latency is acceptable.
Also verify the network:
curl "https://eth.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'For Ethereum Mainnet, the expected result is 0x1.
7. Update Application Code
Direct fetch
Before:
const response = await fetch(process.env.QUICKNODE_ETH_RPC_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getBalance",
params: [address, "latest"],
id: 1
})
});After:
const response = await fetch(process.env.NOWNODES_ETH_RPC_URL, {
method: "POST",
headers: {
"api-key": process.env.NOWNODES_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getBalance",
params: [address, "latest"],
id: 1
})
});viem
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
export const client = createPublicClient({
chain: mainnet,
transport: http(process.env.NOWNODES_ETH_RPC_URL, {
fetchOptions: {
headers: {
"api-key": process.env.NOWNODES_API_KEY
}
}
})
});Small RPC Wrapper
Use a wrapper if your provider library does not pass custom headers cleanly.
export async function rpc(method, params = []) {
const response = await fetch(process.env.NOWNODES_ETH_RPC_URL, {
method: "POST",
headers: {
"api-key": process.env.NOWNODES_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
jsonrpc: "2.0",
id: Date.now(),
method,
params
})
});
if (!response.ok) {
const error = new Error(`RPC HTTP error: ${response.status}`);
error.status = response.status;
throw error;
}
const payload = await response.json();
if (payload.error) {
const error = new Error(payload.error.message);
error.status = 200;
error.code = payload.error.code;
throw error;
}
return payload.result;
}8. Migrate Common Flows
Balances
Use the same eth_getBalance body and change only endpoint/auth.
curl "https://eth.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest"],
"id": 1
}'Check that the result is hex wei and your UI still formats it correctly.
Contract Reads
Use the same eth_call body. If the call targets an old block, use archive access.
curl "https://eth.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x0000000000000000000000000000000000000000",
"data": "0x"
},
"latest"
],
"id": 1
}'Check ABI decoding and revert handling.
Transaction Broadcasting
Use the same eth_sendRawTransaction body.
curl "https://eth.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": ["0xSIGNED_TRANSACTION"],
"id": 1
}'Check chainId, nonce behavior, fee settings, and retry logic before sending production transactions.
Event Indexing
Use eth_getLogs, but avoid very large block ranges.
curl "https://eth.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_getLogs",
"params": [
{
"fromBlock": "0x12D687",
"toBlock": "latest",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
]
}
],
"id": 1
}'Use checkpoints and split large ranges into smaller chunks. After WebSocket reconnects, backfill missed blocks with eth_getLogs.
9. Move WebSocket Subscriptions
QuickNode:
wscat -c "wss://your-endpoint.quiknode.pro/$QUICKNODE_TOKEN/"NOWNodes:
wscat -c "wss://eth.nownodes.io/wss/$NOWNODES_API_KEY"Subscribe to new blocks:
{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}Check:
- the connection opens;
- a subscription id is returned;
- events arrive continuously;
- reconnect works;
- subscriptions are recreated after reconnect;
- missed blocks are backfilled.
10. Replace QuickNode-Specific Features
Streams
QuickNode Streams are managed pipelines. Replace them with a small worker:
NOWNodes RPC/WSS
-> indexing worker
-> filter
-> database / webhook / queue / storageUse:
- WSS
newHeadsfor new block signals; eth_getLogsfor contract events;- Blockbook for address or transaction history where suitable;
- your existing destination, such as PostgreSQL, Kafka, S3, or webhook.
Keep the payload shape stable if downstream services depend on the old Stream output.
Webhooks
QuickNode Webhooks are not regular RPC calls. Replace them with:
- NOWNodes Webhooks, if the required trigger is available;
- WSS + worker;
- polling with
eth_getLogs; - temporary QuickNode fallback while the notification worker is being built.
Use transaction hash + log index as an idempotency key for EVM events.
Marketplace Add-ons
Check every enabled add-on. Some add standard methods, but others expose custom RPC methods or REST APIs.
For each add-on, record:
- method or URL used by the app;
- response fields the app reads;
- replacement in NOWNodes, Blockbook, MarketData, or another API;
- parser changes required by the new response shape.
Do not send custom add-on methods to NOWNodes unless support is verified.
Solana RPC
QuickNode:
curl "https://your-solana-endpoint.quiknode.pro/$QUICKNODE_TOKEN/" \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBalance",
"params": ["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]
}'NOWNodes:
curl "https://sol.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBalance",
"params": ["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]
}'If your app uses Solana gRPC, treat it as a separate migration. Solana JSON-RPC and Yellowstone-compatible gRPC are different interfaces.
11. Archive, Trace, and Debug
Use archive access when the app reads old block state, runs historical eth_call, or indexes from old ranges.
Ethereum archive endpoint example:
https://eth-archive.nownodes.io/Historical balance example:
curl "https://eth-archive.nownodes.io/" \
-X POST \
-H "api-key: $NOWNODES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "0xA00000"],
"id": 1
}'For trace/debug methods:
- List every method currently used.
- Check NOWNodes Trace & Debug support for the same chain.
- Test known transaction hashes.
- Compare response fields before switching parsers.
12. Retries and Rate Limits
QuickNode may return 429 when traffic exceeds plan limits and 413 when payloads or ranges are too large. NOWNodes limits depend on the selected plan.
Before production:
- estimate current request volume from QuickNode usage;
- identify heavy calls such as
eth_getLogs, debug methods, and batch requests; - add 30-50% headroom above current peak usage;
- split large log ranges;
- add retry with backoff for read requests;
- add alerts for
429and5xx.
fn must throw errors with status or code fields. withRetry does not inspect HTTP responses directly. If fn uses fetch, check response.ok inside fn and throw a normalized error before returning the parsed JSON.
async function withRetry(fn, options = {}) {
const maxAttempts = options.maxAttempts ?? 5;
const baseDelayMs = options.baseDelayMs ?? 500;
const maxDelayMs = options.maxDelayMs ?? 8000;
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
return await fn();
} catch (error) {
lastError = error;
const retryable =
error.status === 429 ||
error.status >= 500 ||
error.code === "ETIMEDOUT" ||
error.code === "ECONNRESET";
if (!retryable || attempt === maxAttempts) {
throw error;
}
const jitter = Math.floor(Math.random() * 250);
const delay = Math.min(baseDelayMs * 2 ** (attempt - 1) + jitter, maxDelayMs);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw lastError;
}For eth_sendRawTransaction, retry carefully. Check transaction hash, nonce, and receipt before sending again.
13. Production Rollout
Use a phased rollout:
- Test NOWNodes locally.
- Move staging standard RPC reads.
- Compare selected production reads in shadow mode.
- Move 5-10% of production read traffic.
- Move all read traffic.
- Move transaction broadcasting.
- Move WebSocket/indexers after reconnect and backfill tests.
- Migrate Streams, Webhooks, and Add-ons separately.
Feature flag example:
const provider = process.env.RPC_PROVIDER;
const rpcConfig =
provider === "nownodes"
? {
url: process.env.NOWNODES_ETH_RPC_URL,
headers: {
"api-key": process.env.NOWNODES_API_KEY
}
}
: {
url: process.env.QUICKNODE_ETH_RPC_URL,
headers: {
"Content-Type": "application/json"
}
};Rollback:
- Set
RPC_PROVIDER=quicknode. - Restart affected services.
- Check
eth_blockNumber. - Check transaction broadcasting.
- Check indexer lag and WebSocket subscriptions.
- Keep the NOWNodes config until the issue is diagnosed.
14. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
401 Unauthorized | Missing or invalid api-key header | Send api-key: $NOWNODES_API_KEY |
Wrong chainId | Wrong network endpoint | Use the correct NOWNodes endpoint |
method not found | QuickNode add-on method sent to NOWNodes | Replace the add-on method separately |
413 Content Too Large | Request or block range is too large | Split the request into smaller chunks |
429 Too Many Requests | Rate limit exceeded | Add throttling/backoff or upgrade the plan |
Empty eth_getLogs | Wrong filter, old range, or archive issue | Check topics, split ranges, use archive |
| WSS disconnects | Network interruption or endpoint limits | Reconnect and resubscribe automatically |
| Missed events | No backfill after reconnect | Backfill with eth_getLogs |
| Duplicate Stream/Webhook events | No idempotency key | Use transaction hash + log index |
15. Final Checklist
Before removing QuickNode fallback, confirm:
- RPC and WSS work via NOWNodes, including broadcasting, reconnect, and backfill.
- On-chain data is intact: balances, logs, contract reads, transaction history, and indexer events.
- QuickNode-specific features are replaced or fallback-ready: Streams, Webhooks, IPFS, KV, gRPC, and Marketplace Add-ons.
- Monitoring is stable and rollback has been tested.
- NOWNodes API key is secured; revoke unused QuickNode tokens after the observation period.