Gray Whale

WebSocket Feed

Real-Time

Get live, personalized recommendations streamed directly to your app. The Gray Whale LIM ranks items in real time based on each visitor's behavior — no historical data required.

wss://app.productgenius.io/ws/platform/feed/{project_id}/{visitor_id}/{session_id}

Connection Lifecycle

🔗
Connect
Open WSS
connection
📝
Request
Send pagination
request
📈
Receive
Get ranked
feed items
🔄
Loop
Send linger metrics
+ next request
Keepalive
Ping every
< 2 min
1

Connect

Open a WebSocket connection using the URL below. The three path parameters identify which project to pull from, who the visitor is, and which session is active.
ParameterTypeDescription
project_id string Your Gray Whale project name (e.g. LureLogicII) required
visitor_id UUID Unique identifier for this visitor required
session_id UUID Unique identifier for this browsing session — generate a fresh UUID per session or search prompt required
const project_id = "LureLogicII";
const visitor_id  = "199bd538-30a0-4921-922d-cd81dfc6f308";
const session_id  = "17a6f3c6-1669-44da-862e-fc0d86f1dd29";

const ws = new WebSocket(
  `wss://app.productgenius.io/ws/platform/feed/${project_id}/${visitor_id}/${session_id}`
);

ws.onopen    = ()    => console.log("Connected to Gray Whale feed");
ws.onmessage = (e)   => console.log("Received:", JSON.parse(e.data));
ws.onerror   = (err) => console.error("Error:", err);
ws.onclose   = ()    => console.log("Connection closed");
import asyncio
import websockets

project_id = "LureLogicII"
visitor_id  = "199bd538-30a0-4921-922d-cd81dfc6f308"
session_id  = "17a6f3c6-1669-44da-862e-fc0d86f1dd29"

uri = f"wss://app.productgenius.io/ws/platform/feed/{project_id}/{visitor_id}/{session_id}"

async def main():
    async with websockets.connect(uri) as ws:
        print("Connected to Gray Whale feed")
        async for message in ws:
            print("Received:", message)

asyncio.run(main())
# Install once:
npm install -g wscat

# Connect:
wscat -c "wss://app.productgenius.io/ws/platform/feed/LureLogicII/199bd538-30a0-4921-922d-cd81dfc6f308/17a6f3c6-1669-44da-862e-fc0d86f1dd29"
2

Request Feed with Linger Metrics

Send a socket_pagination_request message to fetch the next batch of ranked items. Include linger metrics — how long each item was visible and how many times it entered the viewport — so the LIM can refine its rankings in real time.
↗ socket_pagination_request SEND
id UUID string Unique ID for this request required
type string "socket_pagination_request" required
search_prompt string Natural language hint to influence ranking
events array Linger metrics and interaction events
⏱ Linger Metric Entry NESTED
enter_count integer Times this item entered the viewport required
id string Item's short tracking hash (from item object) required
time float Total seconds the item was visible required
type string "gray_whale_item" required
One entry per item key (e.g. "fly_001", "fly_002"…)
ws.send(JSON.stringify({
  id:            crypto.randomUUID(),
  type:          "socket_pagination_request",
  search_prompt: "show fly_003 at the very top, first",
  events: [{
    event: "feed linger metrics",
    properties: {
      payload: {
        "fly_001": { enter_count: 1, id: "3MS8", time: 6.84, type: "gray_whale_item" },
        "fly_002": { enter_count: 1, id: "27Y0", time: 6.80, type: "gray_whale_item" },
        "fly_003": { enter_count: 1, id: "96AH", time: 6.88, type: "gray_whale_item" }
      }
    }
  }]
}));
import json, uuid

await ws.send(json.dumps({
  "id":            str(uuid.uuid4()),
  "type":          "socket_pagination_request",
  "search_prompt": "show fly_003 at the very top, first",
  "events": [{
    "event": "feed linger metrics",
    "properties": {
      "payload": {
        "fly_001": {"enter_count": 1, "id": "3MS8", "time": 6.84, "type": "gray_whale_item"},
        "fly_002": {"enter_count": 1, "id": "27Y0", "time": 6.80, "type": "gray_whale_item"},
        "fly_003": {"enter_count": 1, "id": "96AH", "time": 6.88, "type": "gray_whale_item"}
      }
    }
  }]
}))
3

Keepalive Ping

⚠ Timeout: The connection automatically closes after 2 minutes of inactivity. Send a ping at least once every 90 seconds to keep it open.
🔴 ping KEEPALIVE
type string "ping" — that's the entire payload required
Send every ~90 seconds. Server closes connection after 2 min idle.
// Send a ping every 90 seconds
const keepAlive = setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: "ping" }));
  }
}, 90_000);

// Clean up on close
ws.onclose = () => clearInterval(keepAlive);
import asyncio, json

async def keepalive(ws):
    while True:
        await asyncio.sleep(90)
        await ws.send(json.dumps({"type": "ping"}))

# Run alongside your message handler:
asyncio.create_task(keepalive(ws))

Try It Live

Use the interactive console below to test the WebSocket feed requests and responses using live data.
WebSocket Console
Disconnected
--:--:-- Ready. Fill in your credentials above and click Connect.