Back to Blog
webhookswarpindexersui

Indexer as a Service: Real-Time Sui Events

Real-time Sui events without running an indexer. Webhooks, SSE streams, and WebSockets for Sui developers. Filter events before they hit your backend, verify signatures, and react to on-chain activity in milliseconds.

Dimitris, CofounderJanuary 30, 202612 min read

Every Sui application needs to react to on-chain events. A DeFi protocol needs to update positions when swaps happen. A game needs to know when players mint items. A wallet needs to show transactions as they confirm.

The traditional approach? Poll an RPC endpoint every few seconds, parse the results, and hope you didn't miss anything between requests. It works, but it's wasteful, slow, and brittle.

There's a better way. Inodra delivers Sui events to your application in real-time - via webhooks, Server-Sent Events, or WebSockets. You define what you care about, we handle the indexing and delivery.

This post walks through all three delivery methods, shows you how to filter events before they reach your backend, and includes working code examples using a simple PingPong contract on mainnet.

The Three Ways to Receive Events

Different use cases need different delivery mechanisms. Here's what we support and when to use each.

Webhooks

HTTP POST requests to your endpoint whenever matching events occur. Your server receives a JSON payload, processes it, and returns a 2xx status code.

Best for: Backend integrations, serverless functions, async processing pipelines.

Reliability: Automatic retries with exponential backoff - up to 10 attempts over approximately 17 hours. Every webhook includes an HMAC signature so you can verify it came from us.

Trade-off: You need a publicly accessible endpoint. If your server is down, events queue up and retry.

Warp SSE (Server-Sent Events)

A persistent HTTP connection where we push events to you. Your client opens a connection and keeps it open - events arrive the moment they happen.

Best for: Real-time dashboards, monitoring tools, any UI that needs live data without polling.

Reliability: At-least-once delivery. If your connection drops, reconnect with the Last-Event-ID header and we'll replay anything you missed.

Trade-off: Events flow one direction (server to client). If you need to acknowledge receipt, use WebSockets.

Warp WebSocket

Bidirectional connection with an ACK protocol. You receive events and send back acknowledgments - we don't consider an event delivered until you confirm it.

Best for: Exactly-once delivery requirements, trading bots, anything where missing an event is unacceptable.

Reliability: No event is marked delivered until your client ACKs it. If you disconnect without ACKing, the event replays on reconnect.

Trade-off: More complex client implementation. You need to handle the ACK protocol correctly.

Creating a webhook in the Inodra dashboard

What You Can Monitor

Four subscription types cover most use cases:

Event Subscriptions

Monitor Move package events by type. This is the most common pattern - subscribe to a specific event struct from a deployed package.

Example: Track every PingPong event from our demo contract.

Use cases: DeFi protocol events, NFT mints, game actions, governance votes.

Address Subscriptions

Monitor wallet activity. Know when an address sends a transaction or receives objects.

Use cases: Whale watching, user activity feeds, account monitoring.

Coin Subscriptions

Monitor balance changes for a specific token on a specific address.

Use cases: Treasury monitoring, payment confirmations, automated accounting.

Object Subscriptions

Monitor NFT and object state changes - created, mutated, deleted, wrapped, unwrapped.

Use cases: NFT marketplaces, game item tracking, dynamic NFT updates.

Creating an event subscription in the dashboard

Event Payload Filtering

Here's where things get interesting. Most notification systems blast you with everything and expect your backend to filter. That works until you're paying for compute on events you don't care about.

Inodra filters at the source. Define conditions on event fields, and only matching events get delivered.

How It Works

  • Define up to 10 filter conditions per subscription
  • Filters use AND logic - all conditions must match
  • Supports nested fields with dot notation (e.g., nested.inner.value)

Supported Operators

Numeric types (u8 through u256):

  • eq - equals
  • neq - not equals
  • gt - greater than
  • gte - greater than or equal
  • lt - less than
  • lte - less than or equal

Strings and addresses:

  • eq - exact match
  • neq - not equal
  • contains - substring match

Boolean:

  • eq - equals
  • neq - not equals

Practical Example

Let's say you only care about PingPong events where someone sent more than 1 SUI (1 SUI = 1,000,000,000 MIST):

{
  "field": "amount",
  "moveType": "u64",
  "operator": "gt",
  "value": "1000000000"
}

Add this filter to your subscription, and small pings never touch your infrastructure. You only process the events that matter.

Setting up field filters to only receive events where amount > 1 SUI

Index On Demand

Payload filtering requires us to decode and index your Move package's event types. If your package isn't indexed yet and you want to use filters, reach out - we'll index it for you. This is manual right now while we build out self-serve indexing, but we typically turn these around within a day.

Let's Trigger Some Events

Time for working code. We'll use a simple PingPong contract deployed on Sui mainnet:

Package: 0x7438a2f45d1b5548df81e714cbe5bbcb77c0dd25198373acfae368634279d221
Event: 0x7438a2f45d1b5548df81e714cbe5bbcb77c0dd25198373acfae368634279d221::ping_pong::PingPong

The contract is simple. You call ping with some SUI, it emits a PingPong event with your address and the amount, then returns your coins:

public struct PingPong has copy, drop {
    from: address,
    amount: u64,
}

Here's TypeScript to trigger it using Inodra's gRPC gateway:

import { SuiGrpcClient } from "@mysten/sui/grpc";
import { GrpcTransport } from "@protobuf-ts/grpc-transport";
import { ChannelCredentials } from "@grpc/grpc-js";
import { Transaction } from "@mysten/sui/transactions";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";

const PING_PONG_PACKAGE = "0x7438a2f45d1b5548df81e714cbe5bbcb77c0dd25198373acfae368634279d221";
const INODRA_API_KEY = "your_api_key"; // Get yours at inodra.com

async function ping() {
  const transport = new GrpcTransport({
    host: "mainnet-grpc.inodra.com:443",
    channelCredentials: ChannelCredentials.createSsl(),
    meta: { "x-api-key": INODRA_API_KEY },
  });
  const client = new SuiGrpcClient({ network: "mainnet", transport });
  const keypair = Ed25519Keypair.fromSecretKey(/* your secret key */);

  const tx = new Transaction();
  const [coin] = tx.splitCoins(tx.gas, [1_000_000]); // 0.001 SUI
  const [returnedCoin] = tx.moveCall({
    target: `${PING_PONG_PACKAGE}::ping_pong::ping`,
    arguments: [coin],
  });
  tx.transferObjects([returnedCoin], keypair.toSuiAddress());

  const result = await client.signAndExecuteTransaction({
    transaction: tx,
    signer: keypair,
  });
  console.log("TX Digest:", result.Transaction?.digest);
}

ping();

Run either version, and within seconds your webhook fires or your Warp stream pushes the event.

What Your Webhook Receives

When a matching event occurs, we POST to your endpoint with these headers:

Content-Type: application/json
X-Dedupe-Key: {txDigest}:{eventSequence}
X-Inodra-Timestamp: 1706620800
X-Inodra-Signature: t=1706620800,v1=abc123...
User-Agent: Inodra-Notifications/1.0 (+https://inodra.com)

The X-Dedupe-Key header is useful if you need idempotent processing - same key means same event.

The payload structure:

{
  "payloadVersion": 1,
  "payload": {
    "webhookId": "wh_abc123",
    "activityType": "package_event",
    "txDigest": "ABC123...",
    "eventSequence": 0,
    "checkpoint": 12345678,
    "timestamp": 1706620800000,
    "type": "0x7438...::ping_pong::PingPong",
    "sender": "0x1234...",
    "data": {
      "from": "0x1234...",
      "amount": "1000000"
    }
  }
}

The data field contains your decoded event fields. For PingPong, that's the sender's address and the amount in MIST.

Webhook payload received

Verifying Webhook Signatures

Never trust a webhook without verification. Anyone could POST to your endpoint claiming to be Inodra.

Every webhook includes an X-Inodra-Signature header with a timestamp and HMAC-SHA256 signature. Here's how to verify it:

import crypto from "crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const [tPart, vPart] = signature.split(",");
  const timestamp = tPart.replace("t=", "");
  const expectedSig = vPart.replace("v1=", "");

  const computedSig = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${payload}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expectedSig),
    Buffer.from(computedSig)
  );
}

// In your webhook handler
app.post("/webhook", (req, res) => {
  const signature = req.headers["x-inodra-signature"];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send("Invalid signature");
  }

  // Process the event...
  res.status(200).send("OK");
});

The signature includes a timestamp to prevent replay attacks. Consider rejecting webhooks where the timestamp is more than a few minutes old.

Connecting to Warp SSE

If you don't want to expose a webhook endpoint, Warp streams bring events to you over a persistent connection:

const subscriptionId = "sub_abc123"; // from your dashboard
const apiKey = "your_api_key";

// Note: api_key query param is convenient for EventSource which doesn't support custom headers
// For server-side use, prefer the x-api-key header instead
const eventSource = new EventSource(
  `https://mainnet-api.inodra.com/v1/warp/${subscriptionId}/stream?api_key=${apiKey}`
);

eventSource.addEventListener("event", (e) => {
  const data = JSON.parse(e.data);
  console.log("PingPong event:", data);
  // { activityType: "package_event", txDigest: "...", checkpoint: 12345678, ... }
});

eventSource.addEventListener("connected", (e) => {
  const info = JSON.parse(e.data);
  console.log("Connected to Warp stream:", info.subscriptionId);
});

eventSource.onerror = (e) => {
  console.error("Stream error, will auto-reconnect");
};

The browser's EventSource API handles reconnection automatically. If you need to resume from where you left off, the Last-Event-ID header is sent on reconnect - events are retained for 1 hour.

For Node.js, use a library like eventsource that supports the same API.

What's Included in Each Tier

Every tier gets access to the same features - webhooks, Warp streams, payload filtering. The limits scale with your needs:

FeatureFreeTeamProBusiness
Webhooks111525
Warp Streams111525
Event Delivery100 CU100 CU100 CU100 CU
Log Retention0 days7 days30 days90 days

The free tier is real infrastructure. One webhook, one stream, full filtering capabilities. Build your prototype, validate the idea, upgrade when you need more.

For Enterprise Teams

Some teams need more than event delivery. They need custom indexing logic, historical data queries, and infrastructure that scales with their protocol.

We're building toward full indexer-as-a-service:

  • Custom workloads - Run your own indexing logic on our infrastructure
  • Data persistence - We store your indexed data, you query it via API
  • Dedicated resources - No noisy neighbors, predictable performance

If you're an enterprise team that needs custom indexing beyond what webhooks and Warp provide, let's talk.

Get Started

1. Sign up: Create your account - no credit card required for free tier.

2. Create a subscription: Dashboard → Webhooks or Warp → New subscription. Pick event type, add your package and event, optionally add filters.

3. Start receiving events: Point it at your endpoint (webhooks) or connect your client (Warp). Events flow within seconds of on-chain confirmation.

Docs: Webhooks and Warp - full reference for setup and filtering.

Questions? Discord - we're around daily. Plus grab our launch coupon for 50% off for 3 months (limited time).

- Dimitris, Inodra Team

Ready to build on Sui?

Get started with Inodra's blockchain infrastructure today.