Skip to main content
Helius RPC Solana Tutorial: SDK, DAS API, Webhooks - Featured image
Blockchain

Helius RPC Solana Tutorial: SDK, DAS API, Webhooks

BUILDERZ

BUILDERZ

Share

In this guide

The decisions and working steps ahead.

    Why your app needs a dedicated RPC endpoint

    When you don't need it

    Step 1: Install and configure the Helius SDK

Your Solana app works on devnet, then breaks on launch day.

The public endpoint returns 429s. Wallet pages load half their NFTs. Nobody sees a deposit until a user complains.

This tutorial sets up Helius RPC for production: a private endpoint, the SDK, asset reads, pushed events, and readable history. Save it as a checklist for your next launch.

Why your app needs a dedicated RPC endpoint

Public Solana endpoints are fine for testing.

They're shared and rate limited, so they fail first when your traffic spikes. Our Solana RPC guide covers commitment levels, subscriptions, and how RPC nodes work.

A provider gives you a private endpoint keyed to your account.

The Helius endpoints page lists one URL per network:

Code examplebash
# Mainnet HTTP
https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY
# Devnet HTTP
https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY
# Mainnet WebSocket
wss://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY

That URL drops into any Solana client. You also get extra APIs that save you from running your own indexer, and you'll use three of them below.

When you don't need it

A hackathon demo or a read-only script can live on the public endpoint. So can a devnet prototype with one user. Move to a private endpoint once real users, real money, or a launch date enter the picture.

Before you start, you need:

  • An API key from the Helius dashboard
  • Node.js 20 or newer
  • Basic TypeScript
  • A wallet address to test with

Step 1: Install and configure the Helius SDK

The official package is helius-sdk. At the time of writing, the npm registry lists 3.3.0 as the latest release. That release needs Node 20 or newer and lists @solana/kit as a peer dependency.

The Kit dependency matters if your codebase is older. The TypeScript SDK docs say version 2.0.0 was a rewrite on top of @solana/kit. It dropped the dependency on @solana/web3.js versions above 1.73.2, so plan that migration before you upgrade.

Set up a fresh project. helius-sdk 3.3.0 declares @solana/kit ^6.9.0 as a peer on the npm registry, so pin Kit inside that range instead of taking the latest major.

Code examplebash
mkdir helius-demo && cd helius-demo
pnpm init
pnpm add helius-sdk@3.3.0 @solana/kit@^6.9.0
pnpm add -D typescript tsx @types/node

Create one shared client and read the key from the environment:

Code exampletypescript
// src/helius.ts
import { createHelius } from "helius-sdk";

const apiKey = process.env.HELIUS_API_KEY;
if (!apiKey) throw new Error("HELIUS_API_KEY is not set");

export const helius = createHelius({ apiKey });

Keep this file on the server. Your key bills against your account, so it should never ship in a browser bundle. If the frontend needs data, proxy it through your own API route.

Put the key in a .env file for local work and add that file to .gitignore on day one. In production, set it as an environment variable in your host's dashboard. Never paste it into a client component, even for a quick test.

Then run one call before you build anything else:

Code exampletypescript
// src/smoke.ts
import { helius } from "./helius";

const assets = await helius.getAssetsByOwner({
  ownerAddress: process.argv[2],
  page: 1,
  limit: 5,
});

console.log(`Total assets: ${assets.total}`);
Code examplebash
HELIUS_API_KEY=your-key pnpm tsx src/smoke.ts YOUR_WALLET_ADDRESS

If you see a number, your key and network path work.

Step 2: Query wallets and NFTs with the DAS API

The Digital Asset Standard (DAS) API is the reason many apps pick this provider. One interface returns regular NFTs, compressed NFTs, and fungible tokens. The DAS docs include SPL Token and Token-2022 assets in the same API.

Load a wallet in one call

The owner query returns up to 1,000 assets per page, per the getAssetsByOwner reference. Two display options add tokens and the native SOL balance:

Code exampletypescript
// src/wallet.ts
import { helius } from "./helius";

export async function loadWallet(owner: string) {
  const res = await helius.getAssetsByOwner({
    ownerAddress: owner,
    page: 1,
    limit: 100,
    options: { showFungible: true, showNativeBalance: true },
  });

  return res.items.map((asset) => ({
    id: asset.id,
    name: asset.content?.metadata?.name,
    compressed: asset.compression?.compressed ?? false,
  }));
}

Ask for the page size your UI shows, not the maximum. A 100-item page renders faster. You can fetch the next page when the user scrolls.

Batch lookups and compressed NFTs

When you already have mint addresses, use the batch call. The getAssetBatch reference allows up to 1,000 IDs in one request. That turns a loop of single calls into one round trip:

Code exampletypescript
// src/batch.ts
import { helius } from "./helius";

export async function loadMints(mints: string[]) {
  const chunks: string[][] = [];
  for (let i = 0; i < mints.length; i += 1000) chunks.push(mints.slice(i, i + 1000));

  const results = await Promise.all(chunks.map((ids) => helius.getAssetBatch({ ids })));
  return results.flat();
}

The chunking keeps each request under the limit. For a large collection, run the chunks in sequence instead, so you don't hit your rate limit.

Compressed NFTs need one more call. A transfer or burn needs the asset's Merkle proof, which getAssetProof returns:

Code exampletypescript
// src/proof.ts
import { helius } from "./helius";

export async function loadProof(assetId: string) {
  const proof = await helius.getAssetProof({ id: assetId });
  return { root: proof.root, path: proof.proof, tree: proof.tree_id };
}

Fetch the proof right before you build the transaction. The tree can change in between, and a stale proof fails.

Step 3: Push events with webhooks and read history

Polling for new transactions is slow and wastes credits. Webhooks flip the model. The provider sends an HTTP POST to your server when a matching transaction confirms.

The SDK exposes a webhooks client with a create method:

Code exampletypescript
// src/create-webhook.ts
import { helius } from "./helius";

const webhook = await helius.webhooks.create({
  webhookURL: "https://api.yourapp.com/webhooks/helius",
  webhookType: "enhanced",
  transactionTypes: ["TRANSFER", "SWAP", "NFT_SALE"],
  accountAddresses: ["YOUR_TREASURY_WALLET"],
  authHeader: process.env.HELIUS_WEBHOOK_SECRET,
});

console.log("Webhook ID:", webhook.webhookID);

The webhooks FAQ sets the limits for your handler. One webhook can watch up to 100,000 addresses when you create it through the API. Your endpoint must return a 200 within 1 second, and failed deliveries get up to 3 retries.

So you acknowledge first and process later.

Enhanced or raw?

Enhanced webhooks send parsed, readable events. The webhooks overview says raw webhooks have lower latency, because they skip parsing. For new real-time parsed feeds, Helius now points to Parsed Streams, a WebSocket service. Our Helius webhooks guide covers the handler, auth check, queue, and duplicate handling.

Parse transactions into readable events

You'll also need history, like a wallet's past trades. Enhanced Transactions is now a legacy product in maintenance mode. The migration guide moves new code to Parsed Events, at 10 credits per request instead of 100.

The Parsed Events quickstart shows a REST call with up to 100 signatures per batch:

Code exampletypescript
// src/parse.ts
const url = `https://mainnet.helius-rpc.com/v1/parsed-events/transactions?api-key=${process.env.HELIUS_API_KEY}`;

const res = await fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ transactions: [signature], commitment: "confirmed" }),
});

for (const item of await res.json()) {
  if (item.parserStatus === "OK") console.log(item.signature, item.parsed);
  else console.warn(item.signature, item.parserError?.code);
}

Each result carries its own status, so one missing signature doesn't fail the batch.

Store each parsed result next to its signature in your database. Your history page then reads from your own table, and you only call the API for signatures you haven't seen yet.

Step 4: Harden it for production

A demo and a production integration differ in the boring parts. Run this checklist before an RPC integration goes live:

  1. Keep the API key server side and rotate it if it ever reaches a client bundle.
  2. Use separate keys for devnet, staging, and mainnet.
  3. Put every provider call behind one module, so you can add a fallback in one place.
  4. Add timeouts and retries with backoff, and treat a 429 as a signal to slow down.
  5. Check the webhook Authorization header before you touch the body.
  6. Store processed signatures so retried deliveries don't double count.
  7. Log method, latency, and status per call to see what eats your credits.

Point 3 deserves a second look. Any provider can have a bad hour, so keep a second RPC URL ready behind the same module:

Code exampletypescript
// src/rpc.ts
const endpoints = [process.env.PRIMARY_RPC_URL!, process.env.FALLBACK_RPC_URL!];

export async function rpc(method: string, params: unknown[] = []) {
  for (const url of endpoints) {
    try {
      const res = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
        signal: AbortSignal.timeout(5_000),
      });
      if (res.ok) return (await res.json()).result;
    } catch {
      // try the next endpoint
    }
  }
  throw new Error(`All RPC endpoints failed for ${method}`);
}

This only covers standard JSON-RPC reads. DAS and webhooks are provider APIs, so plan a degraded mode for those features.

Send transactions through Sender

Reads are half the job. For writes, the Sender docs describe a service that submits your transaction across several routing paths at once. It doesn't use API credits, because each transaction pays a SOL tip instead.

The tip has a floor. Per the Sender docs, Sender Max needs at least 0.001 SOL and uses every path, while the SWQOS-only mode needs at least 0.000005 SOL. Both modes also need a compute unit price instruction.

FAQ

Is Helius RPC free to use?

Yes, there's a Free plan, and the webhooks docs describe how it differs from paid plans. Each delivered webhook event costs 1 credit on any plan. Plans change, so check the dashboard before you size production traffic.

Do I need the SDK, or can I use plain RPC calls?

You can use the endpoint URL with any Solana client, because it speaks standard JSON-RPC. The SDK adds typed helpers for DAS, webhooks, and sending. Use it for DAS and webhook setup, and plain fetch where the docs show a REST call.

Does helius-sdk work with web3.js?

Recent versions are built on @solana/kit, not web3.js. The docs say the 2.0.0 rewrite removed the dependency on web3.js versions above 1.73.2. If your app is on web3.js 1.x, upgrade the client layer first.

Can the DAS API read compressed NFTs?

Yes, compressed NFTs come back through the same calls as regular NFTs. To transfer or burn one, fetch its Merkle proof first. Do that right before you build the transaction.

Should I still use Enhanced Transactions?

Use Parsed Events for new mainnet code. The legacy API still works but gets no new parser types. The SDK README notes that Parsed Events is mainnet only, so devnet code can stay on the old API for now.

Ship your RPC setup with Builderz

Our public Solana Next.js dApp scaffold is the kind of front end this setup plugs into. The case study lists Next.js, TypeScript, web3.js, and Tailwind, and the code is on GitHub. An app on that stack would need the Kit migration from step 1 first.

Our Solana development service covers the on-chain side and RPC setup. Our web development services cover the Next.js app and API routes around it.

Try the four steps on devnet first. When you want us to build or review the mainnet version, tell us about your Solana project.

Continue reading

Want to turn your idea into reality?

Book a 20-minute intro call, or send a short project brief. We reply within one business day.