Streams Beta
Streams deliver real-time blockchain events to your webhook. Define filters for the on-chain activity you care about — transfers, contract calls, deployments, token events — and Second Layer pushes matching events to your endpoint as each block is processed.
Delivery is at-least-once. Handlers should be idempotent.
Getting started
Create a stream via the SDK. You'll get back a webhook secret for verifying deliveries.
import { SecondLayer } from "@secondlayer/sdk"
const sl = new SecondLayer({ apiKey: "sk-sl_..." })
const { stream, webhookSecret } = await sl.streams.create({
name: "my-stream",
webhookUrl: "https://example.com/webhook",
filters: [
{ type: "stx_transfer" },
],
})Filters
Each stream takes an array of filters. A block matches if any filter matches. Filters narrow by type and optional fields like contract, sender, recipient, or amount.
filters: [
// STX transfers over 1 STX
{ type: "stx_transfer", minAmount: 1_000_000 },
// Calls to a specific contract function
{
type: "contract_call",
contractId: "SP1234...::marketplace",
functionName: "list-asset",
},
// NFT mints from a specific collection
{
type: "nft_mint",
assetIdentifier: "SP1234...::my-nft::nft-token",
},
// Contract deployments by a specific address
{ type: "contract_deploy", deployer: "SP1234..." },
// Print events matching a topic
{ type: "print_event", contractId: "SP1234...::token", topic: "transfer" },
]Webhook payload
Each delivery posts a JSON payload to your webhook URL with the matching block, transactions, and events.
{
streamId: "uuid",
streamName: "my-stream",
block: {
height: 150000,
hash: "0x...",
parentHash: "0x...",
burnBlockHeight: 800000,
timestamp: 1710000000,
},
matches: {
transactions: [{
txId: "0x...",
type: "stx_transfer",
sender: "SP1234...",
status: "success",
contractId: null,
functionName: null,
}],
events: [{
txId: "0x...",
eventIndex: 0,
type: "stx_transfer",
data: { ... },
}],
},
isBackfill: false,
deliveredAt: "2026-03-10T00:00:00Z",
}Management
Streams can be enabled, disabled, updated, and deleted. Use partial IDs for convenience — the SDK resolves them automatically.
// List streams
const { streams } = await sl.streams.list({ status: "active" })
// Get by ID (supports partial IDs)
const stream = await sl.streams.get("a1b2c3")
// Update
await sl.streams.update("a1b2c3", {
webhookUrl: "https://new-endpoint.com/webhook",
filters: [{ type: "stx_transfer", minAmount: 5_000_000 }],
})
// Enable / disable
await sl.streams.enable("a1b2c3")
await sl.streams.disable("a1b2c3")
// Bulk pause / resume all streams
await sl.streams.pauseAll()
await sl.streams.resumeAll()
// Rotate webhook secret
const { secret } = await sl.streams.rotateSecret("a1b2c3")
// Delete
await sl.streams.delete("a1b2c3")Replay
Replay historical blocks through a stream. The webhook payload includes isBackfill: true for replayed deliveries. Maximum 10,000 blocks per replay request.
// Via SDK — replay blocks 150,000 to 151,000
await sl.streams.replay("a1b2c3", {
startBlock: 150_000,
endBlock: 151_000,
})
// Replay failed deliveries
await sl.streams.replayFailed("a1b2c3")CLI
Manage streams from the command line. The CLI generates config files and registers them with the API.
# Generate a new stream config
sl streams new my-stream
# Register from config file
sl streams register ./my-stream.json
# List / get / delete
sl streams ls
sl streams get a1b2c3
sl streams delete a1b2c3
# View delivery logs
sl streams logs a1b2c3
# Replay a block range
sl streams replay a1b2c3 --start 150000 --end 151000
# Rotate webhook secret
sl streams rotate-secret a1b2c3