Setup & Connection
This guide covers how to connect to Warp streams, authenticate, handle events, and implement best practices for reliable real-time streaming.
Authentication
Warp streams require API key authentication via the x-api-key header.
Note: The native browser
EventSourceAPI doesn't support custom headers. UsefetchwithReadableStreamfor browser applications, or theeventsourcenpm package for Node.js.
Using Fetch (Browser & Node.js)
const response = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
headers: { 'x-api-key': apiKey }
})Using eventsource Package (Node.js)
npm install eventsourceconst EventSource = require('eventsource')
const eventSource = new EventSource(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
headers: { 'x-api-key': apiKey }
})Connection Handling
Basic Connection
const streamId = 'your-stream-id'
const apiKey = 'your-api-key'
// AbortController is required for graceful shutdown
const abort = new AbortController()
process.on('SIGINT', () => abort.abort())
const res = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
headers: { 'x-api-key': apiKey },
signal: abort.signal
})
let buffer = ''
for await (const chunk of res.body) {
buffer += new TextDecoder().decode(chunk)
const parts = buffer.split('\n\n')
buffer = parts.pop() // Keep incomplete part
for (const part of parts) {
const dataLine = part.split('\n').find((l) => l.startsWith('data:'))
if (dataLine) console.log(JSON.parse(dataLine.slice(5)))
}
}Closing the Connection
// Clean up when done
abort.abort()Event Types
Warp uses named SSE events. Each event type has a specific structure.
connected - Connection Established
Sent immediately when the connection is established:
{
"connId": "conn_abc123",
"subscriptionId": "sub_xyz789",
"subscriptionType": "event",
"heartbeatIntervalMs": 5000,
"eventType": "0x2::coin::Transfer"
}event - Blockchain Event
Sent when a matching blockchain event occurs:
{
"activityType": "package_event",
"txDigest": "5KmR...",
"eventSequence": 0,
"checkpoint": 12345678,
"timestamp": 1704729600000,
"type": "0x2::coin::Transfer",
"sender": "0xabc...",
"data": {
"amount": "1000000000",
"recipient": "0xdef..."
}
}heartbeat - Keep-Alive
Sent every 5 seconds to maintain the connection:
{
"ts": 1704729630000,
"r": 9500
}ts- Timestamp in millisecondsr- Remaining compute units
quota_exceeded - CU Limit Reached
Sent when your compute unit quota is exhausted:
{
"remaining": 0,
"eventCount": 150,
"heartbeatCount": 10
}Error Handling
Connection Errors
Wrap your fetch in try/catch and handle AbortError separately:
try {
const res = await fetch(url, { headers: { 'x-api-key': apiKey }, signal: abort.signal })
// ... process stream
} catch (err) {
if (err.name === 'AbortError') return // Intentional shutdown
console.error('Connection error:', err)
// Implement reconnection logic
}Manual Reconnection
For custom reconnection logic with exponential backoff:
let abort
let reconnectAttempts = 0
async function connect() {
abort = new AbortController()
try {
const res = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
headers: { 'x-api-key': apiKey },
signal: abort.signal
})
reconnectAttempts = 0
let buffer = ''
for await (const chunk of res.body) {
buffer += new TextDecoder().decode(chunk)
const parts = buffer.split('\n\n')
buffer = parts.pop()
for (const part of parts) {
const dataLine = part.split('\n').find((l) => l.startsWith('data:'))
if (dataLine) console.log(JSON.parse(dataLine.slice(5)))
}
}
} catch (err) {
if (err.name === 'AbortError') return
if (reconnectAttempts++ < 10) {
const delay = Math.min(1000 * 2 ** reconnectAttempts, 30000)
setTimeout(connect, delay)
}
}
}
connect()Node.js Usage
The native EventSource API is browser-only. For Node.js, use the eventsource package:
npm install eventsourceconst EventSource = require('eventsource')
const eventSource = new EventSource(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
headers: {
'x-api-key': apiKey
}
})
// Use addEventListener for named event types
eventSource.addEventListener('event', (e) => {
const data = JSON.parse(e.data)
console.log('Event:', data)
})
eventSource.addEventListener('heartbeat', (e) => {
const data = JSON.parse(e.data)
console.log('Heartbeat:', data.eventCount, 'events received')
})React Integration
Example React hook for Warp streams:
import { useEffect, useState } from 'react'
function useWarpStream(streamId, apiKey) {
const [events, setEvents] = useState([])
const [error, setError] = useState(null)
useEffect(() => {
const abort = new AbortController()
;(async () => {
try {
const res = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
headers: { 'x-api-key': apiKey },
signal: abort.signal
})
let buffer = ''
for await (const chunk of res.body) {
buffer += new TextDecoder().decode(chunk)
const parts = buffer.split('\n\n')
buffer = parts.pop()
for (const part of parts) {
const dataLine = part.split('\n').find((l) => l.startsWith('data:'))
if (dataLine) setEvents((prev) => [...prev, JSON.parse(dataLine.slice(5))].slice(-100))
}
}
} catch (err) {
if (err.name !== 'AbortError') setError('Connection lost')
}
})()
return () => abort.abort()
}, [streamId, apiKey])
return { events, error }
}
// Usage
function LiveEvents({ streamId }) {
const { events, error } = useWarpStream(streamId, process.env.NEXT_PUBLIC_INODRA_API_KEY)
return (
<div>
{error && <div>Error: {error}</div>}
<ul>
{events.map((event, i) => (
<li key={i}>
{event.type} - {event.txDigest}
</li>
))}
</ul>
</div>
)
}Best Practices
1. Handle Reconnections Gracefully
Your application should:
- Implement exponential backoff for repeated failures
- Buffer important events during reconnection
- Use AbortController for clean shutdown
2. Implement Deduplication
Use txDigest to deduplicate events:
const seen = new Set()
function handleEvent(data) {
if (seen.has(data.txDigest)) return
seen.add(data.txDigest)
// Process event...
if (seen.size > 10000) seen.clear() // Prevent memory leak
}3. Monitor Connection Health
Heartbeats arrive every 5 seconds. If none received for 60s, reconnect:
let lastHeartbeat = Date.now()
// In your event loop, update lastHeartbeat when you see heartbeat events
// Check periodically and call abort.abort() + reconnect if staleStream Limits by Plan
Each stream allows 1 concurrent connection. The maximum number of Warp streams you can create depends on your plan:
| Plan | Max Warp Streams | Log Retention |
|---|---|---|
| Free | 1 | None |
| Team | 3 | 7 days |
| Professional | 15 | 30 days |
| Business | 25 | 90 days |
| Enterprise | Custom | Custom |
Reconnection & Event Replay
When you reconnect to a stream, events from your disconnection period can be replayed using the Last-Event-ID header:
- Retention period: 1 hour
- Max buffered events: 10,000 per stream
- Event ID format: Checkpoint number (automatically sent with each event)
Note: The Event ID format (checkpoint number) will change soon to a more granular identifier for better deduplication support. This will allow more precise event tracking and replay.
When using fetch, you can pass the Last-Event-ID header on reconnect to resume from where you left off. Events older than the retention period or exceeding the buffer limit will not be available for replay.
Compute Unit Usage
Warp streams are billed based on compute units:
| Action | Compute Units |
|---|---|
| Event received | 100 CU |
| Heartbeat | 4 CU |
Heartbeats are sent every 5 seconds (~50 CU/minute for keepalive).
Troubleshooting
Connection Refused
- Verify your API key is valid
- Check that the stream ID exists and is active
- Ensure you haven't exceeded connection limits
Events Not Arriving
- Verify the stream is active in the dashboard
- Check that your filter criteria match on-chain activity
- Review the stream type and configuration
Frequent Disconnections
- Check your network stability
- Implement proper reconnection logic
- Consider using a more stable hosting environment
Next Steps
- Warp Overview - Introduction to real-time streaming
- Webhooks - Alternative push-based delivery
- API Reference - Full API documentation