Master Helius RPC, webhooks, and DAS API to build faster, more reliable Solana applications.
TL;DR
- Helius processes $31B+ annual volume through Solana's fastest RPC infrastructure
- Use helius-sdk v2.0.5 for TypeScript with the new @solana/kit integration
- Webhooks monitor up to 100,000 addresses with 80+ transaction types
- DAS API provides unified NFT/cNFT queries—up to 1,000 assets per batch request
- Enhanced transactions parse raw blockchain data into human-readable formats
What You'll Build
This guide teaches you how to integrate Helius's complete API suite into your Solana application. You'll learn to set up the SDK, create real-time webhooks for transaction monitoring, query NFTs with the DAS API, and parse transaction history—all with production-ready TypeScript code.
By the end, you'll have working integrations for each Helius feature that you can deploy immediately.
💡 Prerequisites:
- Node.js 18+ and npm/yarn/pnpm
- Basic TypeScript knowledge
- A free Helius API key from dashboard.helius.dev
- A Solana wallet with devnet SOL for testing
Why Helius Over Standard RPC
Standard Solana RPC endpoints work fine for basic operations. But when you need speed, reliability, and advanced features, the difference becomes clear.
📊 Helius by the numbers:
- 95% faster response times on methods like getProgramAccounts
- 10-80ms latency with global edge routing
- 11 regions worldwide: Pittsburgh, Newark, Salt Lake City, Los Angeles, Vancouver, Dublin, London, Amsterdam, Frankfurt, Singapore, Tokyo
Helius runs traffic through staked validator nodes, giving transactions higher priority during network congestion. Their average latency sits around 140ms with automatic routing to the nearest node and enterprise-grade failover.
For DeFi applications, trading bots, or NFT platforms handling real money, this infrastructure difference translates directly to user experience and transaction success rates.
⚠️ Common mistake: Using free public RPC endpoints in production. These get rate-limited during high traffic, causing transaction failures exactly when reliability matters most. Helius's free tier (1M credits, 10 RPS) handles most development needs.
Project Setup
Let's start with a clean TypeScript project:
# Create project directory
mkdir helius-demo && cd helius-demo
# Initialize with TypeScript
npm init -y
npm install helius-sdk
npm install -D typescript @types/node tsxCreate tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}Your project structure:
helius-demo/
├── src/
│ ├── index.ts
│ ├── webhooks.ts
│ ├── das-api.ts
│ └── enhanced-tx.ts
├── package.json
└── tsconfig.jsonStep 1: Initialize the Helius SDK
The SDK v2.0.0+ uses @solana/kit instead of @solana/web3.js (versions higher than 1.73.2). This is a significant architectural change that improves bundle size and tree-shaking.
// src/index.ts
import { createHelius } from "helius-sdk";
const helius = createHelius({
apiKey: process.env.HELIUS_API_KEY || "your-api-key",
});
// Test the connection
async function testConnection() {
try {
const assets = await helius.getAssetsByOwner({
ownerAddress: "GsbwXfJraMomNxBcpWaJMBgALTyqoq2Np3EQEKMQDZ7M",
page: 1,
limit: 5,
});
console.log(`Found ${assets.items.length} assets`);
console.log("Helius connection successful!");
} catch (error) {
console.error("Connection failed:", error);
}
}
testConnection();Run it:
HELIUS_API_KEY=your-key npx tsx src/index.tsYou should see:
Found 5 assets
Helius connection successful!💪 Pro tip: Store your API key in .env and use dotenv. Never commit API keys to git—Helius keys are tied to your account and have rate limits.
Step 2: Real-Time Webhooks
Webhooks let you monitor on-chain events without polling. Each webhook can track up to 100,000 addresses and filter by 80+ transaction types.
Helius offers three webhook types:
Type | Use Case | Latency
- Enhanced → Human-readable parsed data (NFT sales, swaps) → Standard
- Raw → Full transaction data, all activity → Lower
- Discord → Formatted messages to Discord channels → Standard
Creating a Webhook
// src/webhooks.ts
import { createHelius, WebhookType, TransactionType } from "helius-sdk";
const helius = createHelius({
apiKey: process.env.HELIUS_API_KEY!,
});
async function createNFTSalesWebhook() {
const webhook = await helius.webhooks.createWebhook({
accountAddresses: [
"M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K", // Magic Eden v2
"TSWAPaqyCSx2KABk68Shruf4rp7CxcNi8hAsbdwmHbN", // Tensor
],
webhookURL: "https://your-domain.com/api/webhooks/nft-sales",
transactionTypes: [TransactionType.NFT_SALE, TransactionType.NFT_LISTING],
webhookType: WebhookType.ENHANCED,
});
console.log("Webhook created:", webhook.webhookID);
return webhook;
}
// For lower latency, use raw webhooks with auth headers
async function createRawWebhook() {
const webhook = await helius.webhooks.createWebhook({
accountAddresses: ["YourProgramId111111111111111111111111111111"],
webhookURL: "https://your-domain.com/api/webhooks/raw",
webhookType: WebhookType.RAW,
transactionTypes: [TransactionType.ANY],
authHeader: "Bearer your-secret-token", // Verify webhook authenticity
});
console.log("Raw webhook created:", webhook.webhookID);
return webhook;
}Handling Webhook Payloads
Your webhook endpoint receives JSON payloads. Enhanced webhooks include parsed, human-readable data:
// Example Express.js webhook handler
import express from "express";
const app = express();
app.use(express.json());
interface EnhancedTransaction {
signature: string;
type: string;
description: string;
timestamp: number;
events: {
nft?: {
amount: number;
buyer: string;
seller: string;
nfts: Array<{ mint: string; tokenStandard: string }>;
};
};
}
app.post("/api/webhooks/nft-sales", (req, res) => {
const transactions: EnhancedTransaction[] = req.body;
for (const tx of transactions) {
if (tx.events.nft) {
console.log(`NFT Sale: ${tx.description}`);
console.log(`Buyer: ${tx.events.nft.buyer}`);
console.log(`Price: ${tx.events.nft.amount / 1e9} SOL`);
}
}
res.status(200).send("OK");
});⚠️ Watch out: Helius retries failed webhook deliveries, so you may receive duplicate events. Implement idempotency using the transaction signature as a unique key. Store processed signatures in Redis or your database.
Managing Webhooks
// List all webhooks
const allWebhooks = await helius.webhooks.getAllWebhooks();
// Get specific webhook
const webhook = await helius.webhooks.getWebhookByID("webhook-id");
// Update addresses being monitored
await helius.webhooks.updateWebhook("webhook-id", {
accountAddresses: [...existingAddresses, "newAddress123"],
});
// Delete when no longer needed
await helius.webhooks.deleteWebhook("webhook-id");Step 3: DAS API for NFT Queries
The Digital Asset Standard (DAS) API provides a unified interface for querying all digital assets on Solana—regular NFTs, compressed NFTs (cNFTs), and tokens. No more juggling multiple endpoints.
📊 DAS API capabilities:
- Query up to 1,000 assets per batch request
- Unified interface for NFTs and cNFTs
- Full metadata, royalty info, and ownership data
- Merkle proofs for compressed NFT verification
Get All Assets by Owner
This is the fastest way to fetch a wallet's complete NFT and token holdings:
// src/das-api.ts
import { createHelius } from "helius-sdk";
const helius = createHelius({
apiKey: process.env.HELIUS_API_KEY!,
});
async function getWalletAssets(ownerAddress: string) {
const assets = await helius.getAssetsByOwner({
ownerAddress,
page: 1,
limit: 1000,
displayOptions: {
showFungible: true, // Include SPL tokens
showNativeBalance: true, // Include SOL balance
},
});
console.log(`Total assets: ${assets.total}`);
console.log(`NFTs: ${assets.items.filter(a => a.interface === "V1_NFT").length}`);
console.log(`Tokens: ${assets.items.filter(a => a.interface === "FungibleToken").length}`);
return assets;
}
// Get a specific asset by mint address
async function getAssetDetails(mintAddress: string) {
const asset = await helius.getAsset({ id: mintAddress });
console.log("Asset:", asset.content.metadata.name);
console.log("Collection:", asset.grouping?.[0]?.group_value);
console.log("Royalty:", asset.royalty?.percent, "%");
return asset;
}Query NFT Collections
Get all NFTs in a collection using getAssetsByGroup:
async function getCollectionMints(collectionAddress: string) {
const assets = await helius.getAssetsByGroup({
groupKey: "collection",
groupValue: collectionAddress,
page: 1,
limit: 1000,
});
console.log(`Collection has ${assets.total} NFTs`);
// Extract mint addresses
const mints = assets.items.map((item) => item.id);
return mints;
}Working with Compressed NFTs
Compressed NFTs store data on the Solana ledger (not on-chain accounts), reducing costs by up to 99%. The DAS API handles cNFTs identically to regular NFTs—but you need Merkle proofs for transfers:
async function transferCompressedNFT(assetId: string) {
// Step 1: Get the asset proof
const proof = await helius.getAssetProof({ id: assetId });
console.log("Root:", proof.root);
console.log("Proof length:", proof.proof.length);
// Step 2: Use proof in transfer instruction
// The proof validates the cNFT exists in the Merkle tree
// You'll pass this to @metaplex-foundation/mpl-bubblegum for the actual transfer
return proof;
}
// Batch proof fetching for multiple cNFTs
async function getBatchProofs(assetIds: string[]) {
const proofs = await helius.getAssetProofBatch({ ids: assetIds });
return proofs;
}Search Assets with Filters
The searchAssets method supports complex queries—perfect for token-gating:
async function searchByCreator(creatorAddress: string) {
const assets = await helius.searchAssets({
creatorAddress,
creatorVerified: true,
page: 1,
limit: 100,
});
return assets;
}
// Check if wallet owns an NFT from a collection (token-gating)
async function hasCollectionNFT(
walletAddress: string,
collectionAddress: string
): Promise<boolean> {
const assets = await helius.searchAssets({
ownerAddress: walletAddress,
grouping: ["collection", collectionAddress],
page: 1,
limit: 1,
});
return assets.total > 0;
}💪 Pro tip: Use searchAssets with specific filters for token-gating instead of fetching all assets and filtering client-side. It's faster and uses fewer credits.
Step 4: Enhanced Transaction Parsing
Raw Solana transactions are opaque—instruction data, account indices, and log messages that require decoding. Helius's Enhanced Transactions API transforms these into structured, human-readable data.
📊 Enhanced Transactions API:
- 100+ parsers for different transaction types
- NFT sales, swaps, transfers, staking—all decoded
- Full account balance changes and token movements
- Timestamps, fees, and signatures included
Parse Transaction Signatures
// src/enhanced-tx.ts
import { createHelius } from "helius-sdk";
const helius = createHelius({
apiKey: process.env.HELIUS_API_KEY!,
});
async function parseTransactions(signatures: string[]) {
const transactions = await helius.enhanced.getTransactions({
transactions: signatures,
});
for (const tx of transactions) {
console.log("---");
console.log("Signature:", tx.signature);
console.log("Type:", tx.type);
console.log("Description:", tx.description);
console.log("Fee:", tx.fee / 1e9, "SOL");
// Token transfers are structured
if (tx.tokenTransfers?.length) {
for (const transfer of tx.tokenTransfers) {
console.log(
` Transfer: ${transfer.tokenAmount} ${transfer.mint}`
);
}
}
}
return transactions;
}Get Transaction History for an Address
async function getWalletHistory(address: string) {
const history = await helius.enhanced.getTransactionsByAddress({
address,
limit: 50,
});
// Group by transaction type
const byType = history.reduce((acc, tx) => {
acc[tx.type] = (acc[tx.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log("Transaction breakdown:", byType);
return history;
}
// Filter by specific transaction types
async function getNFTActivity(address: string) {
const history = await helius.enhanced.getTransactionsByAddress({
address,
type: "NFT_SALE", // Only NFT sales
limit: 100,
});
return history;
}Advanced Transaction Queries
For high-traffic applications, use the optimized getTransactionsForAddress:
async function getPaginatedHistory(address: string) {
// First batch
let results = await helius.getTransactionsForAddress(address, {
limit: 100,
});
console.log(`First batch: ${results.length} transactions`);
// Get next page using the last signature
if (results.length === 100) {
const lastSignature = results[results.length - 1].signature;
results = await helius.getTransactionsForAddress(address, {
before: lastSignature,
limit: 100,
});
console.log(`Second batch: ${results.length} transactions`);
}
}⚠️ Watch out: The history endpoint may return incomplete results during high load due to internal timeouts. For critical applications, fetch signatures first with getSignaturesForAddress, then parse in batches with /v0/transactions.
The Complete Code
Here's a production-ready Helius integration class:
// src/helius-client.ts
import { createHelius, WebhookType, TransactionType } from "helius-sdk";
interface HeliusConfig {
apiKey: string;
cluster?: "mainnet-beta" | "devnet";
}
export class HeliusClient {
private helius: ReturnType<typeof createHelius>;
constructor(config: HeliusConfig) {
this.helius = createHelius({
apiKey: config.apiKey,
});
}
// ============ DAS API ============
async getWalletNFTs(ownerAddress: string, page = 1, limit = 100) {
return this.helius.getAssetsByOwner({
ownerAddress,
page,
limit,
displayOptions: {
showFungible: false,
showNativeBalance: false,
},
});
}
async getCollectionNFTs(collectionAddress: string, page = 1) {
return this.helius.getAssetsByGroup({
groupKey: "collection",
groupValue: collectionAddress,
page,
limit: 1000,
});
}
async verifyNFTOwnership(
walletAddress: string,
collectionAddress: string
): Promise<boolean> {
const result = await this.helius.searchAssets({
ownerAddress: walletAddress,
grouping: ["collection", collectionAddress],
page: 1,
limit: 1,
});
return result.total > 0;
}
async getCompressedNFTProof(assetId: string) {
return this.helius.getAssetProof({ id: assetId });
}
// ============ Webhooks ============
async createSalesWebhook(
addresses: string[],
webhookURL: string,
authHeader?: string
) {
return this.helius.webhooks.createWebhook({
accountAddresses: addresses,
webhookURL,
transactionTypes: [TransactionType.NFT_SALE],
webhookType: WebhookType.ENHANCED,
authHeader,
});
}
async createActivityWebhook(
addresses: string[],
webhookURL: string,
authHeader?: string
) {
return this.helius.webhooks.createWebhook({
accountAddresses: addresses,
webhookURL,
transactionTypes: [TransactionType.ANY],
webhookType: WebhookType.RAW,
authHeader,
});
}
async listWebhooks() {
return this.helius.webhooks.getAllWebhooks();
}
async deleteWebhook(webhookId: string) {
return this.helius.webhooks.deleteWebhook(webhookId);
}
// ============ Enhanced Transactions ============
async parseTransaction(signature: string) {
const result = await this.helius.enhanced.getTransactions({
transactions: [signature],
});
return result[0];
}
async getWalletHistory(address: string, limit = 50) {
return this.helius.enhanced.getTransactionsByAddress({
address,
limit,
});
}
// ============ Priority Fees ============
async getPriorityFee(accounts: string[]) {
return this.helius.getPriorityFeeEstimate({
accountKeys: accounts,
options: {
recommended: true,
},
});
}
}
// Usage
const client = new HeliusClient({
apiKey: process.env.HELIUS_API_KEY!,
});
// Check if user owns a Mad Lad
const hasMadLad = await client.verifyNFTOwnership(
"UserWalletAddress...",
"J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w" // Mad Lads collection
);
console.log("Has Mad Lad:", hasMadLad);Run it:
HELIUS_API_KEY=your-key npx tsx src/helius-client.tsWhat Can Go Wrong
Error | Cause | Fix
- 401 Unauthorized → Invalid or missing API key → Check key in dashboard.helius.dev
- 429 Too Many Requests → Rate limit exceeded → Implement backoff or upgrade plan
- Webhook not receiving → URL unreachable or wrong port → Test with ngrok; check firewall
`Empty DAS results` | Pagination starts at 1, not 0 | Use `page: 1` for first page
`cNFT proof missing` | Asset not indexed yet | Wait ~30 seconds after mint
💪 Debugging tip: Helius dashboard shows real-time credit usage and webhook delivery logs. Check there first when things break.
Rate Limits and Best Practices
Helius uses a credit-based system. Different operations cost different credits:
Tier | Credits/Month | RPS Limit | Best For
Free | 1M | 10 | Development, testing
Developer ($49) | 10M | 50 | Side projects, MVPs
Business ($499) | 100M | 200 | Production apps
Key optimization strategies:
- Batch requests when possible—getAssetBatch uses fewer credits than individual calls
- Cache DAS results for assets that don't change frequently
- Use webhooks instead of polling for real-time data
- Set reasonable limits—don't fetch 1,000 items if you need 10
⚠️ Common mistake: RPC and DAS rate limits are independent. If your plan allows 50 RPS for RPC and 10 for DAS, making 50 getAccountInfo calls doesn't affect your DAS quota.
Next Steps
You now have production-ready Helius integrations for:
- Real-time monitoring via webhooks
- NFT queries via DAS API
- Transaction parsing via Enhanced Transactions
Here's where to go next:
- WebSockets — For real-time account and log subscriptions, explore helius.ws namespace methods like accountNotifications() and programNotifications()
- Transaction sending — Use helius.tx.sendTransactionWithSender() for ultra-low latency submission through Helius Sender
- Staking — The SDK includes methods for staking SOL to the Helius validator programmatically
For the full SDK reference, check the Helius documentation.
Resources
- Helius Documentation — Complete API reference
- Helius SDK GitHub — Source code and examples
- Helius Dashboard — API keys and usage monitoring
- DAS API Specification — Digital Asset Standard methods
- Helius Blog — Technical deep dives and updates
Building on Solana? [Builderz](https://builderz.dev) has delivered 32+ Solana projects. Reach out on [Twitter @builderzdotdev](https://twitter.com/builderzdotdev) or check our [portfolio](/portfolio) for examples.



