Realtime updates (WSS via SSE relay)

Frontend connects to your backend via SSE. Your backend connects to OF-V WSS with the server-only JWT.

Why SSE relay?

  • No JWT in the browser. Only your server holds the token.
  • Auto-retry built-in. SSE reconnects automatically on network hiccups.
  • Simple security. Reuse your existing session (HttpOnly cookie) between browser and your backend.

Frontend: Subscribe via SSE (no JWT exposed)

// FRONTEND — listen for payment updates via SSE
const agId = "user@example.com"; // your account/email in OF-V
const es = new EventSource(`/api/wss/stream?ag_id=${encodeURIComponent(agId)}`, { withCredentials: true });

es.onmessage = (evt) => {
  const data = JSON.parse(evt.data);
  console.log("Update:", data);
  if (data.payst === 2) {
    // confirmed
  } else if (data.payst === 3) {
    // canceled/closed
  }
};

es.onerror = (e) => {
  console.warn("SSE error / disconnected, browser will retry.", e);
};

Server: Environment (.env)

PORT=8080

# Server→OF-V (keep this secret — DO NOT expose to browser)
OFV_CLIENT_JWT=<server-only JWT from OF-V>
OFV_WSS=wss://of-v.com:99/jwt/v1/ws

Server: SSE relay — connect to OF-V WSS with server JWT

// server.js (SSE relay)
import "dotenv/config";
import express from "express";
import helmet from "helmet";
import morgan from "morgan";
import WebSocket from "ws";

const app = express();
app.use(helmet());
app.use(morgan("tiny"));

const OFV_WSS = process.env.OFV_WSS;
const SERVER_JWT = process.env.OFV_CLIENT_JWT;

app.get("/api/wss/stream", (req, res) => {
  const agId = req.query.ag_id;
  if (!agId) return res.status(400).end("ag_id required");

  // Set SSE headers
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");
  res.flushHeaders?.();

  let closed = false;
  const wssUrl = `${OFV_WSS}?token=${encodeURIComponent(SERVER_JWT)}`;
  const ws = new WebSocket(wssUrl);

  const hb = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) ws.ping();
  }, 20000);

  ws.on("open", () => {
    ws.send(JSON.stringify({ action: "subscribe", ag_id: agId }));
  });

  ws.on("message", (raw) => {
    try {
      const obj = JSON.parse(raw.toString());
      if (!obj || (obj.ag_id && obj.ag_id !== agId)) return;
      res.write(`data: ${JSON.stringify(obj)}\\n\\n`);
    } catch {}
  });

  ws.on("error", (e) => {
    res.write(`event: error\\ndata: ${JSON.stringify({ error: e.message })}\\n\\n`);
  });

  ws.on("close", () => {
    if (!closed) res.write(`event: end\\ndata: {}\\n\\n`);
    cleanup();
  });

  req.on("close", () => {
    closed = true;
    cleanup();
  });

  function cleanup() {
    clearInterval(hb);
    try { ws.terminate(); } catch {}
    try { res.end(); } catch {}
  }
});

const port = Number(process.env.PORT || 8080);
app.get("/healthz", (_req, res) => res.json({ ok: true }));
app.listen(port, () => console.log("Realtime relay listening on :" + port));

Example message from server

{
  "ag_id": "user@example.com",
  "payst": 2,
  "order_id": 12345,
  "status": "Deposit Confirmed"
}

Common Issues

  • 401 Unauthorized: ensure your backend uses the server-only JWT; no JWT should be in the browser.
  • Mixed Content: if your site is https://, your relay must also be HTTPS.
  • Idle timeouts: keep heartbeats (pings) and allow auto-reconnect (SSE retries by default).

of-v@of-v.com

in fb twitter instragram

© 2025 Yuka.Nikin Coding Studio

Made with   by Yuka.Nikin