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.
Connection Lifecycle
| Parameter | Type | Description | |
|---|---|---|---|
| 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"
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"
required
"gray_whale_item"
required
"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"} } } }] }))
"ping" — that's the entire payload
required
// 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))