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, with 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, since 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-api.opensea.io/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
