WebSocket
WebSocket API
Open media is also available over HTTP as an SSE stream at /api/v1/attester/media/stream (same signing as other attester routes). Some deployments additionally offer a WebSocket endpoint for the same feed; use the wss:// or ws:// URL you were given. The examples use ATTESTIA_WS_URL as a placeholder. Attester services should keep this connection open and treat new-open-assets as the trigger that newly registered media is available for review. The open-assets snapshot is still sent whenever the open set changes.
Connection URL
| Item | Value |
|---|---|
| URL | Your WebSocket base URL is provided with your integration materials. REST calls use https://attestiaprotocol.xyz; the WebSocket host may differ. Local examples often use ws://127.0.0.1:3001 as a stand-in, which is the default port exposed by the Docker Compose gateway service. |
| Subprotocols | None required; the first outbound frame must be the JSON auth object described under Authenticate. |
Authenticate
Send a single JSON text frame immediately after connect:
{
"type": "auth",
"accessKey": "ak_att_0123456789abcdef",
"timestamp": 1735689600000,
"signature": "<lowercase hex HMAC-SHA256>"
}signature uses the same signingSecret as REST. Canonical payload (four lines, UTF-8, newline-separated):
1) <timestamp as string, same as JSON field>
2) CONNECT
3) /__attestia_ws__/<accessKey>
4) <sha256Hex of empty string>After a successful auth, attester connections follow the same eligibility rules as signed REST attester routes (including stake requirements).
Server messages
| type | Description |
|---|---|
| ready | Auth OK; includes wallet echo. |
| new-open-assets | Only newly opened media items (delta vs previous open set). |
| open-assets | Full snapshot { assets: [...] } when the open set changes; use this to trigger review work for newly opened media. |
| ping | Heartbeat when no change. |
| error | Human-readable failure; connection may close with WebSocket code. |
Example client
Pick the language tab that matches your stack. Set ATTESTIA_WS_URL, ATTESTIA_ACCESS_KEY, and ATTESTIA_SIGNING_SECRET in your environment.
import WebSocket from "ws";
import { createHash, createHmac } from "node:crypto";
const WS_URL = process.env.ATTESTIA_WS_URL ?? "ws://127.0.0.1:3001";
const ACCESS_KEY = process.env.ATTESTIA_ACCESS_KEY ?? "";
const SIGNING_SECRET = process.env.ATTESTIA_SIGNING_SECRET ?? "";
function sha256HexUtf8(s: string) {
return createHash("sha256").update(s, "utf8").digest("hex");
}
function wsAuthPayload(ts: string, accessKey: string) {
const ak = accessKey.toLowerCase();
const path = `/__attestia_ws__/${ak}`;
const bodyHash = sha256HexUtf8('');
const lines = [ts, 'CONNECT', path, bodyHash];
return lines.join(String.fromCharCode(10));
}
function signWs(ts: string, accessKey: string) {
const payload = wsAuthPayload(ts, accessKey);
return createHmac("sha256", SIGNING_SECRET).update(payload, "utf8").digest("hex");
}
const ts = String(Date.now());
const ak = ACCESS_KEY.toLowerCase();
const ws = new WebSocket(WS_URL);
ws.on("open", () => {
ws.send(JSON.stringify({
type: "auth",
accessKey: ak,
timestamp: Number(ts),
signature: signWs(ts, ak),
}));
});
ws.on("message", (d) => console.log(String(d)));