Home
How to migrate to NOWNodes

How to migrate from Alchemy to NOWNodes

Learn how to migrate from Alchemy to NOWNodes in a few steps. Follow our simple guide to avoid downtime, and save on blockchain API costs.

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:

Copied!
alchemy.com
ALCHEMY
alchemy_
alchemy-sdk
AlchemyProvider
eth-mainnet.g.alchemy.com

The 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:

MethodMigration
eth_chainIdDirect
eth_blockNumberDirect
eth_getBalanceDirect
eth_callDirect
eth_estimateGasDirect
eth_getLogsDirect, but check ranges
eth_getTransactionReceiptDirect
eth_sendRawTransactionDirect, but test nonce and gas behavior
debug_traceTransactionCheck NOWNodes Trace & Debug support first

Alchemy commonly puts the API key in the URL:

Copied!
https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY

NOWNodes uses a network endpoint plus the api-key header:

Copied!
https://eth.nownodes.io/
api-key: YOUR_NOWNODES_API_KEY

3. What Does Not Move Directly

Do not treat these as simple URL replacements:

Alchemy featureWhat to do instead
alchemy_getAssetTransfersUse eth_getLogs, Blockbook, or your own indexer
alchemy_pendingTransactionsUse standard newPendingTransactions if supported, or filter in your app
alchemy_minedTransactionsUse block subscriptions + transaction filtering
NFT APIReplace with an NFT/data API or your own indexed data
Notify/WebhooksUse NOWNodes Webhooks if suitable, or WSS + worker
Portfolio/Token APIReplace with NOWNodes APIs, Blockbook, or another data layer
Simulation APIReplace with a simulation provider or custom flow
Account Abstraction APIMigrate 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.

NetworkAlchemy HTTPNOWNodes HTTPNOWNodes WSS
Ethereum Mainnethttps://eth-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}https://eth.nownodes.io/wss://eth.nownodes.io/wss/{NOWNODES_API_KEY}
Ethereum Sepoliahttps://eth-sepolia.g.alchemy.com/v2/{ALCHEMY_API_KEY}https://eth-sepolia.nownodes.io/Check NOWNodes docs
Polygon Mainnethttps://polygon-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}https://matic.nownodes.io/wss://matic.nownodes.io/wss/{NOWNODES_API_KEY}
Base Mainnethttps://base-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}https://base.nownodes.io/wss://base.nownodes.io/wss/{NOWNODES_API_KEY}
Arbitrum Onehttps://arb-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}https://arbitrum.nownodes.io/Check NOWNodes docs
Optimism Mainnethttps://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:

Copied!
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_KEY

After:

Copied!
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_KEY

During rollout, keep both providers:

Copied!
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_KEY

This keeps rollback simple.

6. Test the New RPC Endpoint

Check the current Alchemy response:

Copied!
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:

Copied!
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, or 5xx errors appear;
  • latency is acceptable.

Verify the network:

Copied!
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:

Copied!
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:

Copied!
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

Copied!
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.

Copied!
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.

Copied!
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.

Copied!
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.

Copied!
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.

Copied!
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:

Copied!
wscat -c "wss://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY"

NOWNodes:

Copied!
wscat -c "wss://eth.nownodes.io/wss/$NOWNODES_API_KEY"

Subscribe to new blocks:

Copied!
{"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_getLogs for 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:

Copied!
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

Native 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 newPendingTransactions and filter in your app;
  • subscribe to newHeads, read new blocks, and filter transactions after mining;
  • keep alchemy_pendingTransactions temporarily 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:

Copied!
https://eth-archive.nownodes.io/

Historical balance example:

Copied!
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:

  1. List every method currently used.
  2. Check NOWNodes Trace & Debug support for the same chain.
  3. Test known transaction hashes.
  4. 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 429 and 5xx.

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.

Copied!
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:

  1. Test NOWNodes locally.
  2. Move staging read requests.
  3. Compare selected production reads in shadow mode.
  4. Move 5-10% of production read traffic.
  5. Move all read traffic.
  6. Move transaction broadcasting.
  7. Move WebSocket/indexers after reconnect and backfill tests.
  8. Migrate Alchemy-specific APIs separately.

Feature flag example:

Copied!
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:

  1. Set RPC_PROVIDER=alchemy.
  2. Restart affected services.
  3. Check eth_blockNumber.
  4. Check transaction broadcasting.
  5. Check indexer lag and WebSocket subscriptions.
  6. Keep the NOWNodes config until the issue is diagnosed.

14. Troubleshooting

SymptomCauseFix
401 UnauthorizedMissing or invalid api-key headerSend api-key: $NOWNODES_API_KEY
Wrong chainIdWrong network endpointUse the correct NOWNodes endpoint
method not foundAlchemy-specific method sent to NOWNodesReplace that method separately
Empty eth_getLogsWrong filter, old range, or archive issueCheck topics, split ranges, use archive
429 Too Many RequestsRate limit exceededAdd throttling/backoff or upgrade the plan
WSS disconnectsNetwork interruption or endpoint limitsReconnect and resubscribe automatically
Missed eventsNo backfill after reconnectBackfill with eth_getLogs
Missing transfersalchemy_getAssetTransfers was not replaced fullyIndex 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.