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
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
Wire up the Fishjam client and your own authentication. The middleware below verifies the caller and attaches the user, so the next handler can mint a peer token for them.
- Node.js / TypeScript
- Python
constfishjamClient = newFishjamClient ({fishjamId :process .env .FISHJAM_ID !,managementToken :process .env .FISHJAM_MANAGEMENT_TOKEN !, }); constapp =express ();app .use (express .json ()); // parse JSON request bodies // Your own auth (JWT, session, ...) constauthenticateUser = async (req :express .Request ,res :express .Response ,next :express .NextFunction , ) => { consttoken =req .headers .authorization ?.replace ("Bearer ", ""); if (!token ) returnres .status (401).json ({error : "Unauthorized" });req .user =jwt .verify (token ,process .env .JWT_SECRET );next (); };
import os import jwt from fastapi import Body, Depends, FastAPI, Header, HTTPException from fishjam import FishjamClient, PeerOptions app = FastAPI() fishjam_client = FishjamClient( fishjam_id=os.environ["FISHJAM_ID"], management_token=os.environ["FISHJAM_MANAGEMENT_TOKEN"], ) # Your own auth (JWT, session, ...) async def authenticate_user(authorization: str = Header()): token = authorization.removeprefix("Bearer ") try: return jwt.decode(token, os.environ["JWT_SECRET"], algorithms=["HS256"]) except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="Unauthorized")
Create a room and add a peer
Now expose the endpoint your client calls to join the room. Fishjam rooms are identified by an id, so keep a mapping from your own room name to the Fishjam room id and create the room lazily on first join. Then add a peer tagged with the authenticated user and return its peer token.
- Node.js / TypeScript
- Python
// Map your room name to a Fishjam room id (use your database in production) constroomIds = newMap <string,RoomId >(); constgetOrCreateRoom = async (roomName : string):Promise <RoomId > => { constexisting =roomIds .get (roomName ); if (existing ) returnexisting ; constroom = awaitfishjamClient .createRoom ();roomIds .set (roomName ,room .id ); returnroom .id ; }; // Mint a Fishjam peer token for the authenticated userapp .post ("/api/join-room",authenticateUser , async (req ,res ) => { const {roomName } =req .body ; constroomId = awaitgetOrCreateRoom (roomName ); const {peerToken } = awaitfishjamClient .createPeer (roomId , {metadata : {userId :req .user .userId }, });res .json ({peerToken }); });
# Map your room name to a Fishjam room id (use your database in production) room_ids: dict[str, str] = {} def get_or_create_room(room_name: str) -> str: room_id = room_ids.get(room_name) if room_id is not None: return room_id room = fishjam_client.create_room() room_ids[room_name] = room.id return room.id # Mint a Fishjam peer token for the authenticated user @app.post("/api/join-room") async def join_room(room_name: str = Body(embed=True), user=Depends(authenticate_user)): room_id = get_or_create_room(room_name) options = PeerOptions(metadata={"userId": user["userId"]}) _, peer_token = fishjam_client.create_peer(room_id, options) return {"peerToken": peer_token}
(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
Fishjam delivers notifications as a binary application/x-protobuf body. Read the raw body, verify the webhook signature, and decode it with the SDK, then react to the events you care about. Store your webhook secret as FISHJAM_WEBHOOK_SECRET.
- Node.js / TypeScript
- Python
app .post ( "/api/webhooks/fishjam",express .raw ({type : "application/x-protobuf" }), (req :express .Request ,res :express .Response ) => { constsignature =req .headers ["x-fishjam-signature-256"]; if ( typeofsignature !== "string" || !verifyWebhookSignature (req .body ,signature ,process .env .FISHJAM_WEBHOOK_SECRET !, ) ) { returnres .status (401).json ({error : "Invalid signature" }); } for (const {type ,notification } ofdecodeServerNotifications (req .body )) { switch (type ) { case "peerConnected":handlePeerConnected (notification ); break; case "peerDisconnected":handlePeerDisconnected (notification ); break; case "roomDeleted":handleRoomDeleted (notification ); break; default: break; } }res .status (200).json ({received : true }); }, );
import os from fastapi import FastAPI, HTTPException, Request from fishjam import decode_server_notifications, verify_webhook_signature from fishjam.events import ( ServerMessagePeerConnected, ServerMessagePeerDisconnected, ServerMessageRoomDeleted, ) app = FastAPI() @app.post("/api/webhooks/fishjam") async def fishjam_webhook(request: Request): raw_body = await request.body() signature = request.headers.get("x-fishjam-signature-256", "") if not verify_webhook_signature(raw_body, signature, os.environ["FISHJAM_WEBHOOK_SECRET"]): raise HTTPException(status_code=401, detail="Invalid signature") for notification in decode_server_notifications(raw_body): match notification: case ServerMessagePeerConnected(): handle_peer_connected(notification) case ServerMessagePeerDisconnected(): handle_peer_disconnected(notification) case ServerMessageRoomDeleted(): handle_room_deleted(notification) case _: ... return {"received": True}
Enabling webhooks
Now, with your endpoint set up, register its URL in the Webhooks tab of the Fishjam Dashboard. Fishjam then delivers all notifications to that endpoint. We also recommend enabling notification batching when creating rooms:
- Node.js / TypeScript
- Python
constcreateBatchedRoom = async (roomType = "conference") => { constroom = awaitfishjamClient .createRoom ({roomType , // Coalesce notifications into batches for faster delivery and fewer requestsbatchWebhookNotifications : true, }); returnroom ; };
import os from fishjam import FishjamClient, RoomOptions fishjam_client = FishjamClient( fishjam_id=os.environ["FISHJAM_ID"], management_token=os.environ["FISHJAM_MANAGEMENT_TOKEN"], ) def create_batched_room(room_type="conference"): options = RoomOptions( room_type=room_type, # Coalesce notifications into batches for faster delivery and fewer requests batch_webhook_notifications=True, ) return fishjam_client.create_room(options)
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: