Stream Real-Time Events
Listen to marketplace events in real time using @opensea/stream-js.
The OpenSea Stream SDK (@opensea/stream-js) delivers marketplace events over WebSocket in real time — no polling required. Subscribe to listings, sales, transfers, offers, and more by collection or globally.
Streaming events do not count toward your API rate limits.
Prerequisites
- Node.js >= 20.0.0
- OpenSea API key (get one here, or use the instant API key endpoint)
Step 1: Install
npm install @opensea/stream-jsFor Node.js, also install WebSocket and storage dependencies:
npm install ws node-localstorageStep 2: Connect
Browser
import { OpenSeaStreamClient } from "@opensea/stream-js";
const client = new OpenSeaStreamClient({
token: "YOUR_OPENSEA_API_KEY",
});Node.js
import { OpenSeaStreamClient } from "@opensea/stream-js";
import { WebSocket } from "ws";
import { LocalStorage } from "node-localstorage";
const client = new OpenSeaStreamClient({
token: "YOUR_OPENSEA_API_KEY",
connectOptions: {
transport: WebSocket,
sessionStorage: LocalStorage,
},
});Step 3: Subscribe to Events
Subscribe to specific event types on a collection by slug, or use * for all collections.
// New listings for a specific collection
client.onItemListed("boredapeyachtclub", (event) => {
console.log("New listing:", event);
});
// Sales across all collections
client.onItemSold("*", (event) => {
console.log("Sale:", event);
});
// Transfers for a specific collection
client.onItemTransferred("pudgypenguins", (event) => {
console.log("Transfer:", event);
});
// Metadata updates
client.onItemMetadataUpdated("my-collection", (event) => {
console.log("Metadata updated:", event);
});
// Offers received
client.onItemReceivedOffer("boredapeyachtclub", (event) => {
console.log("Offer:", event);
});
// Bids received
client.onItemReceivedBid("*", (event) => {
console.log("Bid:", event);
});Available Event Methods
| Method | Fires when |
|---|---|
onItemListed | A new listing is created |
onItemSold | An item is sold |
onItemTransferred | An item is transferred |
onItemMetadataUpdated | Item metadata changes |
onItemReceivedOffer | An item receives an offer |
onItemReceivedBid | An item receives a bid |
onItemCancelled | A listing or offer is cancelled |
Unsubscribing
Each subscription method returns an unsubscribe function:
const unsubscribe = client.onItemListed("boredapeyachtclub", (event) => {
console.log(event);
});
// Later, stop listening
unsubscribe();Event Payload Structure
Events include key fields like:
{
event_type: "item_listed",
sent_at: "2025-01-15T12:00:00Z",
payload: {
item: {
chain: { name: "ethereum" },
nft_id: "ethereum/0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/1234",
permalink: "https://opensea.io/assets/ethereum/0x.../1234",
metadata: { name: "Bored Ape #1234", image_url: "..." }
},
base_price: "1000000000000000000",
payment_token: { symbol: "ETH", decimals: 18 },
collection: { slug: "boredapeyachtclub" },
maker: { address: "0x..." },
event_timestamp: "2025-01-15T12:00:00Z"
}
}Use event_timestamp for ordering — events can arrive out of order.
Example: Price Alert Bot
Build a bot that watches for listings below a price threshold:
import { OpenSeaStreamClient } from "@opensea/stream-js";
import { WebSocket } from "ws";
import { LocalStorage } from "node-localstorage";
const COLLECTION = "pudgypenguins";
const MAX_PRICE_ETH = 10;
const client = new OpenSeaStreamClient({
token: "YOUR_OPENSEA_API_KEY",
connectOptions: {
transport: WebSocket,
sessionStorage: LocalStorage,
},
});
client.onItemListed(COLLECTION, (event) => {
const priceWei = BigInt(event.payload.base_price);
const priceEth = Number(priceWei) / 1e18;
if (priceEth < MAX_PRICE_ETH) {
console.log(
`Alert: ${event.payload.item.metadata.name} listed at ${priceEth} ETH`,
);
console.log(`Link: ${event.payload.item.permalink}`);
}
});
console.log(`Watching ${COLLECTION} for listings under ${MAX_PRICE_ETH} ETH...`);Reconnection and Best Practices
- Automatic reconnection — The SDK handles reconnection automatically. Lost messages during disconnects are not re-sent (best-effort delivery).
- Heartbeat — The SDK sends heartbeats to keep the connection alive. No manual ping is needed.
- Use
event_timestamp— Events can arrive out of order. Sort byevent_timestampif ordering matters. - Filter server-side — Subscribe to specific collections rather than
*when possible to reduce bandwidth. - Handle errors gracefully — Wrap event handlers in try/catch to prevent a single bad event from crashing your listener.
Using Without the SDK
Any language with a WebSocket client can connect directly:
- Endpoint:
wss://stream.openseabeta.com/socket/websocket?token=<API_KEY> - Heartbeat: Send
{"topic": "phoenix", "event": "heartbeat", "payload": {}, "ref": 0}every 30 seconds - Subscribe: Send
{"topic": "collection:<slug>", "event": "phx_join", "payload": {}, "ref": 0} - Unsubscribe: Send the same message with
"event": "phx_leave"
Streaming vs. Polling
Streaming (@opensea/stream-js) | Polling (REST API) | |
|---|---|---|
| Latency | Real-time (sub-second) | Depends on poll interval |
| Rate limits | Does not count | Counts toward limits |
| Delivery | Best-effort, no replay | Paginated, can backfill |
| Use case | Bots, alerts, live dashboards | Analytics, backfilling history |
For historical data or guaranteed completeness, use the REST events endpoints (GET /api/v2/events/collection/{slug}). For real-time reactions, use streaming.
Next Steps
- @opensea/stream-js on GitHub
- Query Analytics and Events — query historical events via REST
- Buy and Sell NFTs — create listings and fulfill orders programmatically
- Collection Offers and Advanced Trading — collection offers, bulk orders, and more
Updated about 2 hours ago
