Secure Backend Proxy
Use this page when you want a production-safe first integration and do not want raw MediaSFU credentials in the frontend.
Why this matters
MediaSFU is a frontend SDK, but the credential boundary should stay on your backend.
In production, the frontend should:
- collect user intent such as create room or join room
- call your app backend
- receive the backend result
- pass custom
createMediaSFURoomandjoinMediaSFURoomfunctions into the SDK
The backend should:
- hold
MEDIASFU_API_USERNAMEandMEDIASFU_API_KEY - validate the caller and payload
- forward the request to MediaSFU Cloud or your self-hosted MediaSFU Open server
- return only the room result to the frontend
MediaSFU Cloud uses the same upstream endpoint for create and join requests: https://mediasfu.com/v1/rooms/.
Your app can still expose separate /api/mediasfu/create-room and /api/mediasfu/join-room routes if that keeps validation, auth, or auditing simpler. The important contract is that both backend routes forward to the same MediaSFU Cloud rooms URL above.
Recommended request flow
- The user signs into your app.
- Your frontend calls
/api/mediasfu/create-roomor/api/mediasfu/join-room. - Your backend adds MediaSFU credentials server-side.
- Your backend forwards the request to MediaSFU.
- The frontend passes the result into the MediaSFU room flow.
Backend example: Node + Express
This is the smallest production-safe pattern to start from.
import express from 'express';
const app = express();
app.use(express.json());
const mediaSFURoomsUrl =
process.env.MEDIASFU_ROOMS_URL ?? 'https://mediasfu.com/v1/rooms/';
const mediaSFUHeaders = {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.MEDIASFU_API_USERNAME}:${process.env.MEDIASFU_API_KEY}`,
};
async function forwardToMediaSFU(url: string, payload: unknown) {
const response = await fetch(url, {
method: 'POST',
headers: mediaSFUHeaders,
body: JSON.stringify(payload),
});
const data = await response.json();
return { ok: response.ok, status: response.status, data };
}
app.post('/api/mediasfu/create-room', async (req, res) => {
try {
const result = await forwardToMediaSFU(mediaSFURoomsUrl, req.body);
res.status(result.ok ? 200 : result.status).json(result.data);
} catch (error) {
res.status(500).json({
error: `Unable to create room: ${(error as Error).message}`,
});
}
});
app.post('/api/mediasfu/join-room', async (req, res) => {
try {
const result = await forwardToMediaSFU(mediaSFURoomsUrl, req.body);
res.status(result.ok ? 200 : result.status).json(result.data);
} catch (error) {
res.status(500).json({
error: `Unable to join room: ${(error as Error).message}`,
});
}
});
Self-hosted variant
If you are using MediaSFU Open instead of MediaSFU Cloud, point the backend at the room endpoint your server exposes:
MEDIASFU_ROOMS_URL=http://your-mediasfu-open-host/rooms/
The frontend contract stays the same.
Frontend example: custom room hooks
The React package already exposes the right extension points. The important rule is that these functions call your backend, not MediaSFU directly.
type RoomResult = {
data: Record<string, unknown> | null;
success: boolean;
};
export async function createMediaSFURoom({ payload }: { payload: unknown }): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/create-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await response.json();
return { data, success: response.ok };
}
export async function joinMediaSFURoom({ payload }: { payload: unknown }): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await response.json();
return { data, success: response.ok };
}
Frontend example: MediaSFU room setup
import { useState } from 'react';
import { MediasfuGeneric } from 'mediasfu-reactjs';
export function RoomScreen() {
const [sourceParameters, setSourceParameters] = useState<Record<string, unknown>>({});
return (
<MediasfuGeneric
connectMediaSFU={true}
returnUI={false}
noUIPreJoinOptions={{
action: 'create',
eventType: 'conference',
capacity: 10,
duration: 30,
userName: 'Host',
}}
sourceParameters={sourceParameters}
updateSourceParameters={setSourceParameters}
createMediaSFURoom={createMediaSFURoom}
joinMediaSFURoom={joinMediaSFURoom}
/>
);
}
This keeps credentials on the backend while still using the SDK's normal room flow.
Frontend adapter examples
Use the same backend endpoints regardless of SDK or platform. Only the client adapter changes.
React
async function createMediaSFURoom({ payload }: { payload: unknown }) {
const response = await fetch('/api/mediasfu/create-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
async function joinMediaSFURoom({ payload }: { payload: unknown }) {
const response = await fetch('/api/mediasfu/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
Angular
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class MediaSfuProxyService {
constructor(private readonly http: HttpClient) {}
createRoom(payload: unknown) {
return this.http
.post('/api/mediasfu/create-room', payload)
.pipe(map((data) => ({ data, success: true })));
}
joinRoom(payload: unknown) {
return this.http
.post('/api/mediasfu/join-room', payload)
.pipe(map((data) => ({ data, success: true })));
}
}
Vue
type RoomResult = { data: unknown; success: boolean };
export async function createMediaSFURoom(payload: unknown): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/create-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
export async function joinMediaSFURoom(payload: unknown): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return { data: await response.json(), success: response.ok };
}
For SDK-specific component wiring, use the matching SDK page after you confirm the backend boundary is working. Flutter, Kotlin, Swift, Unity, and other non-web clients should follow the same backend boundary with their own networking layer and then pass the results into the SDK-specific room hooks or launch config.
Native integration notes
- Flutter: replace
createMediaSFURoomandjoinMediaSFURoominMediasfuGenericOptionswhen your app should call your backend instead of direct cloud helpers. - Kotlin: let your backend handle auth, room policy, and privileged credentials before you mount the Compose room. Keep the client focused on UI flow and runtime state.
- Swift: let your backend validate or prepare room details first, then hydrate the
MediaSFUIosHostBridgelaunch config your app presents. - Unity: let your app or game perform auth and room selection first, then call
JoinRoomAsyncwith the room details your backend approves.
Flutter example: swap in backend-backed room helpers
MediasfuGeneric(
options: MediasfuGenericOptions(
credentials: credentials,
createMediaSFURoom: createMediaSFURoom,
joinMediaSFURoom: joinMediaSFURoom,
),
)
Minimum production checklist
- Keep MediaSFU credentials in backend environment variables only.
- Authenticate the user before allowing create or join actions.
- Validate room payloads before forwarding them.
- Rate-limit your proxy endpoints.
- Log MediaSFU failures on the backend, not only in the browser.
- Treat the frontend
createMediaSFURoomandjoinMediaSFURoomhooks as thin adapters.
Where to go next
- Need help deciding how much UI to keep? Read the Build Style Guide.
- Need custom runtime control after join works? Read Media Lifecycle.
- Want product-shaped examples after the secure path is working? Read Starter Screens.