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)
- A Fishjam account on the Standard Jar plan (recommended for production deployments)
- Domain and SSL certificates for your backend
- Production database and infrastructure
Step 1: Get your Fishjam credentials
Get credentials from the dashboard
- Log in to Fishjam Dashboard
- Copy your Fishjam ID and Management Token from the dashboard
The Standard Jar plan is recommended for production deployments. With the Mini Jar plan, you can still develop and test using the Sandbox API, but production deployments should use the Standard Jar plan to get full access and reliability guarantees.
Environment variables setup
Create your environment variables:
# Fishjam credentials FISHJAM_ID="your-fishjam-id" FISHJAM_MANAGEMENT_TOKEN="your-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: Replace Sandbox API with proper authentication
Replace Sandbox API calls
Remove any Sandbox API dependencies from your client code:
// ❌ Remove: Sandbox API calls constpeerToken = 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
constfishjamClient = newFishjamClient ({fishjamId :process .env .FISHJAM_ID !,managementToken :process .env .FISHJAM_MANAGEMENT_TOKEN !, }); // Authentication middleware constauthenticateUser = async (req :express .Request ,res :express .Response ,next :express .NextFunction , ) => { try { consttoken =req .headers .authorization ?.replace ("Bearer ", ""); if (!token ) { returnres .status (401).json ({error : "Authentication required" }); } constdecoded =jwt .verify (token ,process .env .JWT_SECRET );req .user = awaitgetUserById (decoded .userId );next (); } catch (error ) {res .status (401).json ({error : "Invalid token" }); } }; // Protected endpoint for joining roomsapp .post ("/api/join-room",authenticateUser , async (req ,res ) => { try { const {roomName } =req .body ; constuser =req .user ; // Create or get room constroom = awaitgetOrCreateRoom (roomName ); // Verify user has permission to join this room if (!canUserJoinRoom (user ,room )) { returnres .status (403).json ({error : "Access denied" }); } // Create peer with user metadata const {peer ,peerToken } = awaitfishjamClient .createPeer (room .id , {metadata : {userId :user .id ,name :user .name ,avatar :user .avatar ,role :user .role , }, });res .json ({peerToken }); } 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 constverifyWebhookSignature = (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:
constcreateRoomWithWebhooks = 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
constnewToken =fishjamClient .refreshPeerToken (roomId ,peerId );
new_token = fishjam_client.refresh_peer_token(room_id, peer_id)
See also
For scaling considerations:
For specific backend frameworks: