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

ItemValue
URLYour 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.
SubprotocolsNone required; the first outbound frame must be the JSON auth object described under Authenticate.

Authenticate

Send a single JSON text frame immediately after connect:

Request (client → server)
{
  "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

typeDescription
readyAuth OK; includes wallet echo.
new-open-assetsOnly newly opened media items (delta vs previous open set).
open-assetsFull snapshot { assets: [...] } when the open set changes; use this to trigger review work for newly opened media.
pingHeartbeat when no change.
errorHuman-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.

Gateway auth + receive loop
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)));

Limits & errors