How to Deploy Fishjam to Production
How-to Guide - Deploy your Fishjam backend safely to production
This guide covers the essential steps to move from development (using the Sandbox API) to a production-ready Fishjam backend deployment.
Prerequisites
- Working Fishjam backend (see Backend Quick Start)
- Production Fishjam environment (not Sandbox)
- Domain and SSL certificates for your backend
- Production database and infrastructure
Step 1: Set up production Fishjam environment
Get production credentials
- Log in to Fishjam Dashboard
- Create or select your Production environment (not Sandbox)
- Copy your production Fishjam URL and Management Token
- Note: These are different from your sandbox credentials
Environment variables setup
Create production environment variables:
# Production Fishjam credentials FISHJAM_URL="your-production-fishjam-url" FISHJAM_MANAGEMENT_TOKEN="your-production-management-token" # Your application settings NODE_ENV="production" PORT="3000" DATABASE_URL="your-production-database-url" # Security settings JWT_SECRET="your-secure-jwt-secret" CORS_ORIGIN="https://yourdomain.com"
Step 2: Implement proper authentication
Replace Sandbox API calls
Remove any Sandbox API dependencies from your client code:
// ❌ Remove: Sandbox API calls const
peerToken = awaitgetSandboxPeerToken (roomName ,userName ); // ✅ Replace with: Your authenticated API constresponse = awaitfetch ("https://your-backend.com/api/join-room", {method : "POST",headers : { "Content-Type": "application/json",Authorization : `Bearer ${userToken }`, },body :JSON .stringify ({roomName ,userName }), });
Implement user authentication
// @noErrors: 2339 const jwt = {} as any; import { FishjamClient, Room } from "@fishjam-cloud/js-server-sdk"; import express from "express"; const app = express(); const getUserById = async (userId: string) => ({ id: userId, name: "User", avatar: "", role: "user", }); const getOrCreateRoom = async (roomName: string) => ({ id: "room-id" }) as unknown as Room; const canUserJoinRoom = (user: any, room: any) => true; process.env.FISHJAM_URL = ""; process.env.FISHJAM_MANAGEMENT_TOKEN = ""; process.env.JWT_SECRET = ""; // ---cut--- const fishjamClient = new FishjamClient({ fishjamUrl: process.env.FISHJAM_URL!, managementToken: process.env.FISHJAM_MANAGEMENT_TOKEN!, }); // Authentication middleware const authenticateUser = async ( req: express.Request, res: express.Response, next: express.NextFunction, ) => { try { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) { return res.status(401).json({ error: "Authentication required" }); } const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = await getUserById(decoded.userId); next(); } catch (error) { res.status(401).json({ error: "Invalid token" }); } }; // Protected endpoint for joining rooms app.post("/api/join-room", authenticateUser, async (req, res) => { try { const { roomName } = req.body; const user = req.user; // Create or get room const room = await getOrCreateRoom(roomName); // Verify user has permission to join this room if (!canUserJoinRoom(user, room)) { return res.status(403).json({ error: "Access denied" }); } // Create peer with user metadata const { peer, peerToken } = await fishjamClient.createPeer(room.id, { metadata: { userId: user.id, name: user.name, avatar: user.avatar, role: user.role, }, }); res.json({ peerToken, fishjamUrl: process.env.FISHJAM_URL, }); } catch (error) { console.error("Join room error:", error); res.status(500).json({ error: "Failed to join room" }); } });
(Optional) Step 3: Handle webhooks and events
You may wish to receive events from Fishjam regarding created rooms and peers. All you need for this is a single api endpoint:
Webhook endpoint
// Webhook signature verification const
verifyWebhookSignature = (req :express .Request ,res :express .Response ,next :express .NextFunction , ) => { constsignature =req .headers ["x-fishjam-signature"]; constpayload =JSON .stringify (req .body ); constexpectedSignature =crypto .createHmac ("sha256",process .env .WEBHOOK_SECRET ) .update (payload , "utf8") .digest ("hex"); if (signature !==expectedSignature ) { returnres .status (401).json ({error : "Invalid signature" }); }next (); }; // Webhook handlerapp .post ( "/api/webhooks/fishjam",express .raw ({type : "application/json" }),verifyWebhookSignature , (req :express .Request ,res :express .Response ) => { constevent =req .body ; switch (event .type ) { case "peer_connected":handlePeerConnected (event .data ); break; case "peer_disconnected":handlePeerDisconnected (event .data ); break; case "room_empty":handleRoomEmpty (event .data ); break; default:console .log ("Unhandled event type:",event .type ); }res .status (200).json ({received : true }); }, );
Enabling webhooks
Now, with your endpoint setup, all you need to do is supply your webhook endpoint to Fishjam when creating a room:
const
createRoomWithWebhooks = async (roomName : string,roomType = "conference", ) => { constroom = awaitfishjamClient .createRoom ({roomType ,webhookUrl : `${process .env .BASE_URL }/api/webhooks/fishjam`, }); returnroom ; };
Common production issues
Issue: Token expiration handling
Peer tokens expire 24h after creation. We encourage keeping room and peer lifetimes as short as possible (typically a single room corresponds to a single video call or stream). However, if you wish to reuse a single peer over multiple days, you can make use of token refreshing:
- Node.js / TypeScript
- Python
import { FishjamClient, RoomId, PeerId } from "@fishjam-cloud/js-server-sdk"; const fishjamClient = new FishjamClient({ fishjamUrl: "", managementToken: "", }); const roomId = "" as RoomId; const peerId = "" as PeerId; // ---cut--- const newToken = fishjamClient.refreshPeerToken(roomId, peerId);
new_token = fishjam_client.refresh_peer_token(room_id, peer_id)
See also
For scaling considerations:
For specific backend frameworks: