Stream Real-Time Events
Listen to marketplace events in real time using @opensea/sdk/stream.
The OpenSea Stream API delivers marketplace events over WebSocket in real time, with no polling required. Subscribe to listings, sales, transfers, offers, and more by collection or globally.
The client ships inside the OpenSea SDK at the @opensea/sdk/stream subpath. It was previously published as @opensea/stream-js, which is now deprecated.
Streaming events do not count toward your API rate limits.
Prerequisites
- Node.js >= 22.0.0, or any browser
- OpenSea API key (get one here, or use the instant API key endpoint)
Step 1: install
npm install @opensea/sdkNo other dependencies are needed. Earlier versions of this guide told Node users to install ws and node-localstorage; neither is required on Node 22 or newer.
Step 2: connect
The same code works in the browser and in Node.js. A global WebSocket is used automatically.
import { OpenSeaStreamClient } from "@opensea/sdk/stream";
const client = new OpenSeaStreamClient({
apiKey: "YOUR_OPENSEA_API_KEY",
});On a runtime older than Node 22, supply a WebSocket implementation:
import { OpenSeaStreamClient } from "@opensea/sdk/stream";
import { WebSocket } from "ws";
const client = new OpenSeaStreamClient({
apiKey: "YOUR_OPENSEA_API_KEY",
connectOptions: { transport: WebSocket },
});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);
});
// 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 |
onItemReceivedBid | An item receives an offer or bid |
onItemCancelled | A listing or offer is cancelled |
onCollectionOffer | A collection-wide offer is made |
onTraitOffer | An offer is made on a trait |
onOrderInvalidate | An order becomes invalid |
onOrderRevalidate | A previously invalid order becomes valid again |
onEvents(slug, eventTypes, callback) subscribes to several event types with one callback, filtered server-side. Its callback receives the payload untyped, so check event_type and narrow.
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/sdk/stream";
const COLLECTION = "pudgypenguins";
const MAX_PRICE_ETH = 10;
const client = new OpenSeaStreamClient({
apiKey: "YOUR_OPENSEA_API_KEY",
});
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 reconnects with backoff and re-subscribes to every topic it was watching. 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 in your handlers: A handler that throws is caught and reported through
onError, so it no longer takes down the connection or skips your other handlers. Catching inside your handler is still how you decide what a failure should do.
Using without the SDK
Any language with a WebSocket client can connect directly:
- Endpoint:
wss://stream-api.opensea.io/socket/websocket?token=<API_KEY>&vsn=2.0.0 - Subscribe: send
["1", "1", "collection:<slug>", "phx_join", {}] - Unsubscribe: send the same frame with
"phx_leave"in place of"phx_join" - Heartbeat: send
[null, "2", "phoenix", "heartbeat", {}]every 30 seconds, and reconnect if a reply does not arrive before the next one is due
Frames are JSON arrays of [join_ref, ref, topic, event, payload]. The server answers a join with ["1", "1", "collection:<slug>", "phx_reply", {"status": "ok", "response": {}}], then sends events on the same topic with the event name in the fourth position. Responses come back as arrays whether or not you pass vsn=2.0.0, so parse them as arrays either way.
See the Stream API reference for more detail.
Streaming vs. polling
Streaming (@opensea/sdk/stream) | 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 SDK on GitHub
- Migrating from @opensea/stream-js
- 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 2 days ago
