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).
Related guides
Support
© 2025 Yuka.Nikin Coding Studio
Made with by Yuka.Nikin