Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/api/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ The notifications can be configured using Webhook or Websocket.

#### Webhook

When using webhooks for receiving notifications, the `webhookUrl` must be passed
in the `RoomConfig` options when creating a room.
When using webhooks for receiving notifications, configure your webhook URL in
the **Webhooks** tab of the [Fishjam Dashboard](https://fishjam.io/app).
Fishjam then delivers all notifications to that URL.

The HTTP POST to the `webhookUrl` uses "application/x-protobuf" content type.
The HTTP POST to your webhook URL uses "application/x-protobuf" content type.
The body is binary data, that represents encoded `ServerMessage`.

Setting `batchWebhookNotifications` to `true` in the `RoomConfig` is recommended. Fishjam then coalesces several notifications into one POST: the body is still a single `ServerMessage`, but its `notification_batch` field holds a `NotificationBatch`, which carries the individual notifications as a repeated list of `ServerMessage`s (see `server_notifications.proto`). This delivers notifications faster and with fewer requests. The SDK decoders (`decodeServerNotifications` / `decode_server_notifications`) unwrap the batch for you, returning the notifications as a flat list — so a single notification and a batch are handled the same way.
Setting `batchWebhookNotifications` to `true` in the `RoomConfig` is recommended. Fishjam then coalesces several notifications into one POST: the body is still a single `ServerMessage`, but its `notification_batch` field holds a `NotificationBatch`, which carries the individual notifications as a repeated list of `ServerMessage`s (see `server_notifications.proto`). This delivers notifications faster and with fewer requests. The SDK decoders unwrap the batch for you.

For more information see also [server setup documentation](../how-to/backend/server-setup#webhooks)

Expand Down
17 changes: 9 additions & 8 deletions docs/how-to/backend/fastapi-example.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,29 @@ async def get_rooms(fishjam_client: Annotated[FishjamClient, Depends(fishjam_cli

## Listening to events

Fishjam instance is a stateful server that is emitting messages upon certain events.
You can listen for those messages and react as you prefer.
There are two options for obtaining these.
Fishjam emits messages upon certain events — see [Listening to events](../../how-to/backend/server-setup#listening-to-events) for how delivery, batching, and signing work.

### Webhooks

To receive and parse Fishjam protobuf messages, create a dependency function that decodes the request body using the `decode_server_notifications` function from the `fishjam` package.
It returns a list of notifications and transparently unwraps batched payloads (rooms created with `batch_webhook_notifications=True`), so you can iterate over the result and use pattern matching to respond to different kinds of events.
Create a dependency function that [verifies the webhook signature](../../how-to/backend/server-setup#verifying-webhook-signatures) and decodes the request body into notifications, then use pattern matching to respond to different kinds of events.

:::note
`decode_server_notifications` replaces the now-deprecated `receive_binary`. It always returns a list — a single notification comes back as a one-element list — which lets the same handler work whether or not batching is enabled.
`decode_server_notifications` replaces the now-deprecated `receive_binary`.
:::

```python
from fastapi import Depends, FastAPI, Request
from fishjam import decode_server_notifications
import os
from fastapi import Depends, FastAPI, HTTPException, Request
from fishjam import decode_server_notifications, verify_webhook_signature
from fishjam.events import ServerMessagePeerAdded

app = FastAPI()

async def parse_fishjam_notifications(request: Request):
binary = await request.body()
signature = request.headers.get("x-fishjam-signature-256", "")
if not verify_webhook_signature(binary, signature, os.environ["FISHJAM_WEBHOOK_SECRET"]):
raise HTTPException(status_code=401, detail="Invalid webhook signature")
return decode_server_notifications(binary)

@app.post("/fishjam-webhook")
Expand Down
27 changes: 20 additions & 7 deletions docs/how-to/backend/fastify-example.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,18 @@ fastify.listen({ port: 3000 });

## Listening to events

Fishjam instance is a stateful server that is emitting messages upon certain events.
You can listen for those messages and react as you prefer.
There are two options to obtain these.
Fishjam emits messages upon certain events — see [Listening to events](../../how-to/backend/server-setup#listening-to-events) for how delivery, batching, and signing work.

### Webhooks

To receive and parse the Fishjam protobuf messages, add a content type parser to your global (or scoped) Fastify instance.
Use `decodeServerNotifications` to decode the request body: it returns a list of typed notifications and transparently unwraps a [batch](../../how-to/backend/server-setup#webhooks), so rooms created with `batchWebhookNotifications: true` are handled the same as single notifications — a single notification simply arrives as a one-element list.
Then, you will be able to iterate over the parsed notifications at `request.body`.
Add a content type parser to your global (or scoped) Fastify instance that [verifies the webhook signature](../../how-to/backend/server-setup#verifying-webhook-signatures) and decodes the body into typed notifications.
You can then iterate over the parsed notifications at `request.body`.

```ts title='main.ts'
import Fastify, { FastifyRequest } from "fastify";
import {
decodeServerNotifications,
verifyWebhookSignature,
ServerNotification,
} from "@fishjam-cloud/js-server-sdk";

Expand All @@ -133,7 +131,22 @@ const fastify = Fastify();
fastify.addContentTypeParser(
"application/x-protobuf",
{ parseAs: "buffer" },
async (_: FastifyRequest, body: Buffer) => decodeServerNotifications(body),
async (request: FastifyRequest, body: Buffer) => {
const signature = request.headers["x-fishjam-signature-256"];
if (
typeof signature !== "string" ||
!verifyWebhookSignature(
body,
signature,
process.env.FISHJAM_WEBHOOK_SECRET!,
)
) {
throw Object.assign(new Error("Invalid webhook signature"), {
statusCode: 401,
});
}
return decodeServerNotifications(body);
},
);

fastify.post<{ Body: ServerNotification[] }>("/fishjam-webhook", (request) => {
Expand Down
39 changes: 29 additions & 10 deletions docs/how-to/backend/production-deployment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,17 @@ 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.
Fishjam delivers notifications as a binary `application/x-protobuf` body. Read the raw body, [verify the webhook signature](../../how-to/backend/server-setup#verifying-webhook-signatures), and decode it with the SDK, then react to the events you care about. Store your webhook secret as `FISHJAM_WEBHOOK_SECRET`.

<Tabs groupId="language">
<TabItem value="ts" label="Node.js / TypeScript">

```typescript
import express from "express";
import { decodeServerNotifications } from "@fishjam-cloud/js-server-sdk";
import {
decodeServerNotifications,
verifyWebhookSignature,
} from "@fishjam-cloud/js-server-sdk";

const app = express();
const handlePeerConnected = {} as any;
Expand All @@ -245,6 +248,18 @@ Fishjam delivers notifications as a binary `application/x-protobuf` body. Read t
"/api/webhooks/fishjam",
express.raw({ type: "application/x-protobuf" }),
(req: express.Request, res: express.Response) => {
const signature = req.headers["x-fishjam-signature-256"];
if (
typeof signature !== "string" ||
!verifyWebhookSignature(
req.body,
signature,
process.env.FISHJAM_WEBHOOK_SECRET!,
)
) {
return res.status(401).json({ error: "Invalid signature" });
}

for (const { type, notification } of decodeServerNotifications(req.body)) {
switch (type) {
case "peerConnected":
Expand All @@ -271,8 +286,9 @@ Fishjam delivers notifications as a binary `application/x-protobuf` body. Read t
<TabItem value="python" label="Python">

```python
from fastapi import FastAPI, Request
from fishjam import decode_server_notifications
import os
from fastapi import FastAPI, HTTPException, Request
from fishjam import decode_server_notifications, verify_webhook_signature
from fishjam.events import (
ServerMessagePeerConnected,
ServerMessagePeerDisconnected,
Expand All @@ -283,7 +299,12 @@ Fishjam delivers notifications as a binary `application/x-protobuf` body. Read t

@app.post("/api/webhooks/fishjam")
async def fishjam_webhook(request: Request):
for notification in decode_server_notifications(await request.body()):
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)
Expand All @@ -301,7 +322,7 @@ Fishjam delivers notifications as a binary `application/x-protobuf` body. Read t

### 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:
Now, with your endpoint set up, register its URL in the **Webhooks** tab of the [Fishjam Dashboard](https://fishjam.io/app). Fishjam then delivers all notifications to that endpoint. We also recommend enabling [notification batching](../../how-to/backend/server-setup#webhooks) when creating rooms:

<Tabs groupId="language">
<TabItem value="ts" label="Node.js / TypeScript">
Expand All @@ -311,10 +332,9 @@ Now, with your endpoint setup, all you need to do is supply your webhook endpoin
const fishjamClient = {} as any;

// ---cut---
const createRoomWithWebhooks = async (roomType = "conference") => {
const createBatchedRoom = async (roomType = "conference") => {
const room = await fishjamClient.createRoom({
roomType,
webhookUrl: `${process.env.BASE_URL}/api/webhooks/fishjam`,
// Coalesce notifications into batches for faster delivery and fewer requests
batchWebhookNotifications: true,
});
Expand All @@ -336,10 +356,9 @@ Now, with your endpoint setup, all you need to do is supply your webhook endpoin
management_token=os.environ["FISHJAM_MANAGEMENT_TOKEN"],
)

def create_room_with_webhooks(room_type="conference"):
def create_batched_room(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,
)
Expand Down
60 changes: 54 additions & 6 deletions docs/how-to/backend/server-setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,9 @@ There are two options to obtain these.

### Webhooks

Simply pass your webhook URL as a `webhookUrl` parameter when creating a room.
Configure your webhook URL in the **Webhooks** tab of the [Fishjam Dashboard](https://fishjam.io/app). Fishjam then delivers all notifications to that URL.

We recommend also enabling **notification batching** via `batchWebhookNotifications`. Fishjam then coalesces several notifications into a single request, delivering them faster and with fewer HTTP requests — which improves your backend's response time under load.
We recommend also enabling **notification batching** when creating a room. Fishjam then coalesces several notifications into a single request, delivering them faster and with fewer HTTP requests — which improves your backend's response time under load.

<Tabs groupId="language">
<TabItem value="ts" label="Typescript">
Expand All @@ -241,8 +241,7 @@ We recommend also enabling **notification batching** via `batchWebhookNotificati
managementToken: "bbb",
});
// ---cut---
const webhookUrl = "https://example.com/";
await fishjamClient.createRoom({ webhookUrl, batchWebhookNotifications: true });
await fishjamClient.createRoom({ batchWebhookNotifications: true });
```

</TabItem>
Expand All @@ -253,7 +252,6 @@ We recommend also enabling **notification batching** via `batchWebhookNotificati
from fishjam import RoomOptions

options = RoomOptions(
webhook_url="https://example.com/",
batch_webhook_notifications=True,
)

Expand All @@ -264,7 +262,7 @@ We recommend also enabling **notification batching** via `batchWebhookNotificati

</Tabs>

On the receiving side, decode the raw request body with `decodeServerNotifications` (Node) or `decode_server_notifications` (Python), then iterate the result and react to the events you care about. Both decoders return a list of notifications and transparently unwrap a batch — a single notification simply comes back as a one-element list, so the same handler works whether or not batching is enabled.
On the receiving side, decode the raw request body with the SDK's decoder, then iterate the result and react to the events you care about. The decoder returns a list of notifications and transparently unwraps a batch — a single notification simply comes back as a one-element list, so the same handler works whether or not batching is enabled.

<Tabs groupId="language">
<TabItem value="ts" label="Typescript">
Expand Down Expand Up @@ -319,6 +317,56 @@ On the receiving side, decode the raw request body with `decodeServerNotificatio

</Tabs>

#### Verifying webhook signatures

Every webhook request is signed so you can confirm it really came from Fishjam. Each delivery carries an `x-fishjam-signature-256: sha256=<hex>` header — an HMAC-SHA256 of the **raw request body**, keyed with your webhook secret. Find (and rotate) that secret in the **Webhooks** tab of the [Fishjam Dashboard](https://fishjam.io/app).

Verify the header against the raw body **before** decoding, and reject mismatches with `401`. Verification needs the exact bytes Fishjam sent, so read the raw body before any parsing.

<Tabs groupId="language">
<TabItem value="ts" label="Typescript">

```ts
declare const rawBody: Buffer;
declare const signatureHeader: string;
// ---cut---
import { verifyWebhookSignature, decodeServerNotifications } from '@fishjam-cloud/js-server-sdk';

const secret = process.env.FISHJAM_WEBHOOK_SECRET!;

// rawBody: Buffer, signatureHeader: req.headers['x-fishjam-signature-256']
if (!verifyWebhookSignature(rawBody, signatureHeader, secret)) {
// respond with 401 here — how depends on your framework, see the examples linked below
throw new Error('Invalid webhook signature');
}

// signature is valid — safe to decode
const notifications = decodeServerNotifications(rawBody);
```

</TabItem>

<TabItem value="python" label="Python">

```python
import os
from fishjam import verify_webhook_signature, decode_server_notifications

secret = os.environ["FISHJAM_WEBHOOK_SECRET"]

# raw_body: bytes, signature_header: request.headers["x-fishjam-signature-256"]
if not verify_webhook_signature(raw_body, signature_header, secret):
# respond with 401 here — how depends on your framework, see the examples linked below
raise PermissionError("Invalid webhook signature")

# signature is valid — safe to decode
notifications = decode_server_notifications(raw_body)
```

</TabItem>

</Tabs>

See the [Fastify](../../how-to/backend/fastify-example#webhooks) and [FastAPI](../../how-to/backend/fastapi-example#webhooks) examples for a full webhook handler wired into a web framework.

### SDK Notifier
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/backend-quick-start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ Here's a complete working backend:
Now that you have a working backend, explore these guides:

- [How to set up a production deployment](../how-to/backend/production-deployment)
- [How to handle webhooks and events](../how-to/backend/server-setup)
- [How to handle webhooks and events](../how-to/backend/server-setup#webhooks)
- [How to implement a FastAPI backend](../how-to/backend/fastapi-example)
- [How to implement a Fastify backend](../how-to/backend/fastify-example)

Expand Down
2 changes: 1 addition & 1 deletion packages/js-server-sdk
Submodule js-server-sdk updated 82 files
+10 −0 AGENTS.md
+29 −9 packages/fishjam-openapi/openapi.sh
+1 −1 packages/fishjam-openapi/openapitools.json
+2 −2 packages/fishjam-openapi/package.json
+0 −4 packages/fishjam-openapi/src/generated/.gitignore
+0 −1 packages/fishjam-openapi/src/generated/.npmignore
+56 −7 packages/fishjam-openapi/src/generated/.openapi-generator/FILES
+1 −1 packages/fishjam-openapi/src/generated/.openapi-generator/VERSION
+0 −2,811 packages/fishjam-openapi/src/generated/api.ts
+68 −0 packages/fishjam-openapi/src/generated/apis/CredentialsApi.ts
+86 −0 packages/fishjam-openapi/src/generated/apis/MoQApi.ts
+621 −0 packages/fishjam-openapi/src/generated/apis/RoomsApi.ts
+217 −0 packages/fishjam-openapi/src/generated/apis/StreamersApi.ts
+255 −0 packages/fishjam-openapi/src/generated/apis/StreamsApi.ts
+96 −0 packages/fishjam-openapi/src/generated/apis/TrackForwardingsApi.ts
+217 −0 packages/fishjam-openapi/src/generated/apis/ViewersApi.ts
+9 −0 packages/fishjam-openapi/src/generated/apis/index.ts
+0 −86 packages/fishjam-openapi/src/generated/base.ts
+0 −150 packages/fishjam-openapi/src/generated/common.ts
+0 −110 packages/fishjam-openapi/src/generated/configuration.ts
+0 −57 packages/fishjam-openapi/src/generated/git_push.sh
+3 −16 packages/fishjam-openapi/src/generated/index.ts
+90 −0 packages/fishjam-openapi/src/generated/models/AgentOutput.ts
+52 −0 packages/fishjam-openapi/src/generated/models/AudioFormat.ts
+53 −0 packages/fishjam-openapi/src/generated/models/AudioSampleRate.ts
+83 −0 packages/fishjam-openapi/src/generated/models/CompositionInfo.ts
+66 −0 packages/fishjam-openapi/src/generated/models/ModelError.ts
+75 −0 packages/fishjam-openapi/src/generated/models/MoqAccess.ts
+73 −0 packages/fishjam-openapi/src/generated/models/MoqAccessConfig.ts
+158 −0 packages/fishjam-openapi/src/generated/models/Peer.ts
+83 −0 packages/fishjam-openapi/src/generated/models/PeerConfig.ts
+95 −0 packages/fishjam-openapi/src/generated/models/PeerConfigAgent.ts
+95 −0 packages/fishjam-openapi/src/generated/models/PeerConfigVAPI.ts
+95 −0 packages/fishjam-openapi/src/generated/models/PeerConfigWebRTC.ts
+74 −0 packages/fishjam-openapi/src/generated/models/PeerDetailsResponse.ts
+91 −0 packages/fishjam-openapi/src/generated/models/PeerDetailsResponseData.ts
+90 −0 packages/fishjam-openapi/src/generated/models/PeerOptionsAgent.ts
+93 −0 packages/fishjam-openapi/src/generated/models/PeerOptionsVapi.ts
+83 −0 packages/fishjam-openapi/src/generated/models/PeerOptionsWebRTC.ts
+74 −0 packages/fishjam-openapi/src/generated/models/PeerRefreshTokenResponse.ts
+66 −0 packages/fishjam-openapi/src/generated/models/PeerRefreshTokenResponseData.ts
+53 −0 packages/fishjam-openapi/src/generated/models/PeerStatus.ts
+54 −0 packages/fishjam-openapi/src/generated/models/PeerType.ts
+114 −0 packages/fishjam-openapi/src/generated/models/Room.ts
+122 −0 packages/fishjam-openapi/src/generated/models/RoomConfig.ts
+74 −0 packages/fishjam-openapi/src/generated/models/RoomCreateDetailsResponse.ts
+74 −0 packages/fishjam-openapi/src/generated/models/RoomCreateDetailsResponseData.ts
+74 −0 packages/fishjam-openapi/src/generated/models/RoomDetailsResponse.ts
+57 −0 packages/fishjam-openapi/src/generated/models/RoomType.ts
+74 −0 packages/fishjam-openapi/src/generated/models/RoomsListingResponse.ts
+116 −0 packages/fishjam-openapi/src/generated/models/Stream.ts
+89 −0 packages/fishjam-openapi/src/generated/models/StreamConfig.ts
+74 −0 packages/fishjam-openapi/src/generated/models/StreamDetailsResponse.ts
+95 −0 packages/fishjam-openapi/src/generated/models/Streamer.ts
+74 −0 packages/fishjam-openapi/src/generated/models/StreamerDetailsResponse.ts
+66 −0 packages/fishjam-openapi/src/generated/models/StreamerToken.ts
+74 −0 packages/fishjam-openapi/src/generated/models/StreamsListingResponse.ts
+53 −0 packages/fishjam-openapi/src/generated/models/SubscribeMode.ts
+66 −0 packages/fishjam-openapi/src/generated/models/SubscribeTracksRequest.ts
+75 −0 packages/fishjam-openapi/src/generated/models/Subscriptions.ts
+91 −0 packages/fishjam-openapi/src/generated/models/Track.ts
+74 −0 packages/fishjam-openapi/src/generated/models/TrackForwarding.ts
+91 −0 packages/fishjam-openapi/src/generated/models/TrackForwardingInfo.ts
+53 −0 packages/fishjam-openapi/src/generated/models/TrackType.ts
+53 −0 packages/fishjam-openapi/src/generated/models/VideoCodec.ts
+75 −0 packages/fishjam-openapi/src/generated/models/Viewer.ts
+74 −0 packages/fishjam-openapi/src/generated/models/ViewerDetailsResponse.ts
+66 −0 packages/fishjam-openapi/src/generated/models/ViewerToken.ts
+48 −0 packages/fishjam-openapi/src/generated/models/index.ts
+449 −0 packages/fishjam-openapi/src/generated/runtime.ts
+3 −1 packages/js-server-sdk/package.json
+63 −77 packages/js-server-sdk/src/client.ts
+10 −8 packages/js-server-sdk/src/exceptions/index.ts
+43 −32 packages/js-server-sdk/src/exceptions/mapper.ts
+5 −2 packages/js-server-sdk/src/index.ts
+36 −0 packages/js-server-sdk/src/webhook.ts
+7 −12 packages/js-server-sdk/tests/config-validation.test.ts
+81 −0 packages/js-server-sdk/tests/exceptions.test.ts
+65 −0 packages/js-server-sdk/tests/serialization.test.ts
+33 −1 packages/js-server-sdk/tests/webhook.test.ts
+1 −10 release-automation/bump-version.sh
+0 −2 yarn.lock
Loading