How to migrate from Alchemy to NOWNodes
This tutorial shows how to move from Alchemy to NOWNodes without rewriting your whole Web3 stack. Standard JSON-RPC requests usually need only a new endpoint and a new authentication method. Alchemy-specific APIs, such as Transfers API, NFT API, Notify/Webhooks, Portfolio API, Token API, Simulation API, and alchemy_pendingTransactions, should be migrated separately.
1. Start with a Quick Audit
Before changing code, check what your app currently uses in Alchemy.
Look for:
- HTTP RPC URLs like
https://eth-mainnet.g.alchemy.com/v2/{apiKey}; - WSS URLs like
wss://eth-mainnet.g.alchemy.com/v2/{apiKey}; - standard JSON-RPC methods:
eth_blockNumber,eth_getBalance,eth_call,eth_getLogs,eth_sendRawTransaction; - Alchemy-specific methods:
alchemy_getAssetTransfers,alchemy_pendingTransactions,alchemy_minedTransactions; - NFT API, Transfers API, Token API, Portfolio API, Simulation API, Notify/Webhooks;
- current request volume, Compute Units, errors, and alerts.
Search your codebase for:
alchemy.com
ALCHEMY
alchemy_
alchemy-sdk
AlchemyProvider
eth-mainnet.g.alchemy.comThe important split is simple: standard RPC can move first; Alchemy-specific features need their own replacement.
2. What Moves Directly
These standard 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 ranges |
eth_getTransactionReceipt | Direct |
eth_sendRawTransaction | Direct, but test nonce and gas behavior |
debug_traceTransaction | Check NOWNodes Trace & Debug support first |
Alchemy commonly puts the API key in the URL:
https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEYNOWNodes 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:
| Alchemy feature | What to do instead |
|---|---|
alchemy_getAssetTransfers | Use eth_getLogs, Blockbook, or your own indexer |
alchemy_pendingTransactions | Use standard newPendingTransactions if supported, or filter in your app |
alchemy_minedTransactions | Use block subscriptions + transaction filtering |
| NFT API | Replace with an NFT/data API or your own indexed data |
| Notify/Webhooks | Use NOWNodes Webhooks if suitable, or WSS + worker |
| Portfolio/Token API | Replace with NOWNodes APIs, Blockbook, or another data layer |
| Simulation API | Replace with a simulation provider or custom flow |
| Account Abstraction API | Migrate separately; this is not standard node RPC |
Move standard RPC first. Keep Alchemy-specific APIs as temporary fallback until each one has a replacement.
4. Endpoint Mapping
Use the NOWNodes endpoint for the same network.
| Network | Alchemy HTTP | NOWNodes HTTP | NOWNodes WSS |
|---|---|---|---|
| Ethereum Mainnet | https://eth-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY} | https://eth.nownodes.io/ | wss://eth.nownodes.io/wss/{NOWNODES_API_KEY} |
| Ethereum Sepolia | https://eth-sepolia.g.alchemy.com/v2/{ALCHEMY_API_KEY} | https://eth-sepolia.nownodes.io/ | Check NOWNodes docs |
| Polygon Mainnet | https://polygon-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY} | https://matic.nownodes.io/ | wss://matic.nownodes.io/wss/{NOWNODES_API_KEY} |
| Base Mainnet | https://base-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY} | https://base.nownodes.io/ | wss://base.nownodes.io/wss/{NOWNODES_API_KEY} |
| Arbitrum One | https://arb-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY} | https://arbitrum.nownodes.io/ | Check NOWNodes docs |
| Optimism Mainnet | https://opt-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY} | https://optimism.nownodes.io/ | wss://optimism.nownodes.io/wss/{NOWNODES_API_KEY} |
Always verify the network with eth_chainId before sending real traffic.
5. Configure Environment Variables
Before:
ALCHEMY_ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY
ALCHEMY_ETH_WSS_URL=wss://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEYAfter:
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:
RPC_PROVIDER=nownodes
ALCHEMY_ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY
NOWNODES_ETH_RPC_URL=https://eth.nownodes.io/
NOWNODES_API_KEY=YOUR_NOWNODES_API_KEYThis keeps rollback simple.
6. Test the New RPC Endpoint
Check the current Alchemy response:
curl "https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY" \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'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 Alchemy by block number;
- no
401,403,429, or5xxerrors appear; - latency is acceptable.
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.ALCHEMY_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 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 the 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
}'Before production transactions, check chainId, nonce, fee settings, and retry behavior.
Event Indexing
Use eth_getLogs, but avoid huge 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
Alchemy:
wscat -c "wss://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY"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 connection, subscription id, reconnect, resubscribe, and backfill.
10. Replace Alchemy-Specific Features
alchemy_getAssetTransfers
This method returns normalized address transfer history. A standard node does not expose the same response in one call.
Use:
eth_getLogsfor ERC-20/ERC-721/ERC-1155 transfer events;- Blockbook if it matches the chain and response you need;
- your own indexer for production-grade address history;
- temporary Alchemy fallback while the indexer is being built.
For ERC-20 transfers, start with the Transfer event topic:
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efNative transfers and internal transfers need separate handling.
alchemy_pendingTransactions
Alchemy can filter pending transactions by fromAddress and toAddress. Standard subscriptions usually do not provide the same filtered stream.
Options:
- use
newPendingTransactionsand filter in your app; - subscribe to
newHeads, read new blocks, and filter transactions after mining; - keep
alchemy_pendingTransactionstemporarily if mempool filtering is critical.
NFT, Token, Portfolio, Notify
These are product APIs, not standard node RPC.
For each API, write down:
- endpoint or method used;
- response fields your app reads;
- replacement source;
- parser changes;
- fallback plan.
11. Archive, Trace, and Debug
Use archive access when the app reads old block state, runs historical eth_call, or indexes 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:
- 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
Alchemy uses Compute Units. NOWNodes uses request volume and plan limits. Recalculate usage before production.
Before rollout:
- check Alchemy usage for the last 30 days;
- identify heavy calls such as
eth_getLogs, Transfers API, and debug methods; - add 30-50% headroom above 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 read requests.
- 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 Alchemy-specific APIs 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.ALCHEMY_ETH_RPC_URL,
headers: {
"Content-Type": "application/json"
}
};Rollback:
- Set
RPC_PROVIDER=alchemy. - 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 | Alchemy-specific method sent to NOWNodes | Replace that method separately |
Empty eth_getLogs | Wrong filter, old range, or archive issue | Check topics, split ranges, use archive |
429 Too Many Requests | Rate limit exceeded | Add throttling/backoff or upgrade the plan |
| WSS disconnects | Network interruption or endpoint limits | Reconnect and resubscribe automatically |
| Missed events | No backfill after reconnect | Backfill with eth_getLogs |
| Missing transfers | alchemy_getAssetTransfers was not replaced fully | Index native, ERC, and internal transfers separately |
15. Final Checklist
Before removing Alchemy 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.
- Alchemy-specific APIs, including Transfers, NFT, Notify/Webhooks, Token, Portfolio, and Simulation APIs, are replaced or have a tested fallback.
- Monitoring is stable and rollback has been tested.
- NOWNodes API key is secured; revoke unused Alchemy keys after the observation period.