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 and decode it with the SDK, then react to the events you care about.
- Node.js / TypeScript
- Python
app .post ( "/api/webhooks/fishjam",express .raw ({type : "application/x-protobuf" }), (req :express .Request ,res :express .Response ) => { 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 }); }, );
from fastapi import FastAPI, Request from fishjam import decode_server_notifications from fishjam.events import ( ServerMessagePeerConnected, ServerMessagePeerDisconnected, ServerMessageRoomDeleted, ) app = FastAPI() @app.post("/api/webhooks/fishjam") async def fishjam_webhook(request: Request): for notification in decode_server_notifications(await request.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 setup, all you need to do is supply your webhook endpoint to Fishjam when creating a room. We also recommend enabling batchWebhookNotifications, which delivers notifications faster and with fewer HTTP requests for a better backend response time under load:
- Node.js / TypeScript
- Python
constcreateRoomWithWebhooks = async (roomType = "conference") => { constroom = awaitfishjamClient .createRoom ({roomType ,webhookUrl : `${process .env .BASE_URL }/api/webhooks/fishjam`, // 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_room_with_webhooks(room_type="conference"): options = RoomOptions( room_type=room_type, webhook_url=f"{os.environ['BASE_URL']}/api/webhooks/fishjam", # 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: