@baxcloud/baxcloud-server-sdk
v1.0.3
Published
BaxCloud Server SDK for Node.js — webhooks, rooms, streaming, ingress, and PK Battle
Maintainers
Readme
@baxcloud/baxcloud-server-sdk
Official BaxCloud Server SDK for Node.js — webhook verification and API client for rooms, streaming, ingress, and PK Battle.
Product-specific SDKs:
- @baxcloud/baxstream — docs — video conversion + AI
- @baxcloud/baxmail — docs — transactional email
- @baxcloud/baxverify — docs — SMS OTP verification
- @baxcloud/baxlinks — docs — deep linking & attribution
Features
- 🔐 Webhook Signature Verification - Secure HMAC-SHA256 signature verification with constant-time comparison
- 🚀 Express/Connect Middleware - Drop-in middleware for webhook handling
- 🛠️ API Client - Full-featured client for room and participant management
- 📘 TypeScript Support - Complete type definitions included
- ⚡ Zero Dependencies - Uses only Node.js built-in modules
Installation
npm install @baxcloud/baxcloud-server-sdkQuick Start
Webhook Verification
Basic Usage
import { verifyWebhookSignature } from '@baxcloud/baxcloud-server-sdk';
const isValid = verifyWebhookSignature({
secret: process.env.BAXCLOUD_WEBHOOK_SECRET,
payload: req.rawBody, // Raw request body as string
signature: req.headers['x-baxcloud-signature']
});
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}Using Express Middleware
import express from 'express';
import { webhookMiddleware } from '@baxcloud/baxcloud-server-sdk';
const app = express();
app.post('/webhooks/baxcloud',
// IMPORTANT: Capture raw body for signature verification
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}),
// Verify signature and parse event
webhookMiddleware(process.env.BAXCLOUD_WEBHOOK_SECRET),
(req, res) => {
// Event is automatically verified and parsed
const event = req.baxcloudEvent;
console.log('Event type:', event.event);
console.log('Project ID:', event.projectId);
console.log('Room name:', event.room?.name);
console.log('Participant:', event.participant?.identity);
// Handle the event
switch (event.event) {
case 'room.created':
console.log('Room created:', event.room.name);
break;
case 'participant.joined':
console.log('Participant joined:', event.participant.identity);
break;
case 'participant.left':
console.log('Participant left:', event.participant.identity);
break;
// ... handle other events
}
res.json({ received: true });
}
);
app.listen(3000);Manual Parsing with Verification
import { parseWebhook } from '@baxcloud/baxcloud-server-sdk';
app.post('/webhooks/baxcloud',
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}),
async (req, res) => {
try {
const event = parseWebhook({
secret: process.env.BAXCLOUD_WEBHOOK_SECRET,
payload: req.rawBody,
signature: req.headers['x-baxcloud-signature']
});
// Event is verified and parsed
console.log('Received event:', event.event);
res.json({ received: true });
} catch (error) {
console.error('Webhook error:', error.message);
return res.status(401).json({ error: error.message });
}
}
);API Client
Initialize Client
import { BaxCloudServerClient } from '@baxcloud/baxcloud-server-sdk';
const client = new BaxCloudServerClient({
apiKey: process.env.BAXCLOUD_API_KEY,
projectId: process.env.BAXCLOUD_PROJECT_ID!,
// Optional: request timeout in ms (default: 30000)
timeout: 30000
});Create a Room
const room = await client.createRoom({
name: 'my-meeting-room',
maxParticipants: 50,
enableRecording: true,
emptyTimeout: 300, // Close room after 5 minutes when empty
metadata: JSON.stringify({ type: 'meeting', host: 'user-123' })
});
console.log('Room created:', room);Generate Access Token
const token = await client.generateToken({
roomName: 'my-meeting-room',
participantIdentity: 'user-123',
participantName: 'John Doe',
participantMetadata: JSON.stringify({ role: 'host' }),
ttl: 3600, // 1 hour
grants: {
canPublish: true,
canSubscribe: true,
canPublishData: true,
canUpdateOwnMetadata: true
}
});
// Send token to client
res.json({ token });Room Management
// Get room details
const room = await client.getRoom('my-meeting-room');
// List all rooms
const rooms = await client.listRooms();
// Update room metadata
await client.updateRoomMetadata('my-meeting-room',
JSON.stringify({ status: 'in-progress' })
);
// Delete room
await client.deleteRoom('my-meeting-room');Participant Management
// List participants in a room
const participants = await client.listParticipants('my-meeting-room');
// Update participant metadata
await client.updateParticipantMetadata(
'my-meeting-room',
'user-123',
JSON.stringify({ role: 'presenter' })
);
// Remove participant from room
await client.removeParticipant('my-meeting-room', 'user-123');BaxStream — use @baxcloud/baxstream
Video conversion (HLS), AI categorization, and content moderation live in the dedicated BaxStream SDK. Install it alongside this package when you need those APIs:
npm install @baxcloud/baxstreamimport { BaxCloudStreamClient } from '@baxcloud/baxstream';
const stream = new BaxCloudStreamClient({
projectId: process.env.BAXCLOUD_PROJECT_ID!,
apiKey: process.env.BAXCLOUD_API_KEY!,
});
const job = await stream.createVideoConversionJob({
inputUrl: 'https://example.com/video.mp4',
outputFormats: ['360p', '720p'],
s3Config: { bucket: '...', accessKey: '...', secretKey: '...' },
});BaxStream webhooks (video.conversion.*, categorization.*, moderation.*) are verified with this server SDK — see the BaxStream Webhook Events section below.
Complete Example: Express Server
import express from 'express';
import { BaxCloudServerClient, webhookMiddleware } from '@baxcloud/baxcloud-server-sdk';
const app = express();
const client = new BaxCloudServerClient({
apiKey: process.env.BAXCLOUD_API_KEY
});
// Endpoint to create room and generate token
app.post('/api/join-room', express.json(), async (req, res) => {
try {
const { roomName, userId, userName } = req.body;
// Create room if it doesn't exist
try {
await client.createRoom({
name: roomName,
maxParticipants: 10
});
} catch (error) {
// Room might already exist, that's okay
}
// Generate access token
const token = await client.generateToken({
roomName,
participantIdentity: userId,
participantName: userName,
ttl: 3600,
grants: {
canPublish: true,
canSubscribe: true,
canPublishData: true
}
});
res.json({ token });
} catch (error) {
console.error('Error:', error);
res.status(500).json({ error: error.message });
}
});
// Webhook endpoint
app.post('/webhooks/baxcloud',
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}),
webhookMiddleware(process.env.BAXCLOUD_WEBHOOK_SECRET),
async (req, res) => {
const event = req.baxcloudEvent;
console.log(`[Webhook] ${event.event}`, {
projectId: event.projectId,
room: event.room?.name,
participant: event.participant?.identity
});
// Handle events
switch (event.event) {
case 'room.created':
// Log room creation
console.log('Room created:', event.room.name);
break;
case 'participant.joined':
// Track participant join
console.log(`${event.participant.identity} joined ${event.room.name}`);
break;
case 'participant.left':
// Track participant leave
console.log(`${event.participant.identity} left ${event.room.name}`);
break;
case 'room.ended':
// Clean up after room ends
console.log('Room ended:', event.room?.name);
break;
}
res.json({ received: true });
}
);
app.listen(3000, () => {
console.log('Server running on port 3000');
});Webhook Events
Import event name constants for type-safe switch handling:
import { BAXCLOUD_WEBHOOK_EVENTS, type BaxCloudWebhookEventType } from '@baxcloud/baxcloud-server-sdk';
switch (event.event) {
case BAXCLOUD_WEBHOOK_EVENTS.ROOM_CREATED:
break;
case BAXCLOUD_WEBHOOK_EVENTS.VIDEO_CONVERSION_COMPLETED:
break;
case BAXCLOUD_WEBHOOK_EVENTS.BAXMAIL_DELIVERED:
break;
}BAXCLOUD_WEBHOOK_EVENT_NAMES lists every supported event. WEBHOOK_EVENT_CATEGORIES groups them for UI or docs.
Realtime (rooms, media, egress, ingress, PK)
| Event | Description |
|-------|-------------|
| room.created | Room created and active |
| room.ended | Room session ended |
| participant.joined | Participant joined |
| participant.left | Participant left |
| track.published | Audio/video track published |
| track.unpublished | Track unpublished |
| recording.started | Recording started |
| recording.updated | Recording status updated |
| recording.completed | Recording finished; file ready |
| recording.failed | Recording failed |
| stream.started | RTMP stream out started |
| stream.updated | Stream out status updated |
| stream.completed | Stream out completed |
| stream.failed | Stream out failed |
| live.started | Ingress (OBS/FFmpeg) started |
| live.updated | Ingress status updated |
| live.ended | Ingress ended |
| live.failed | Ingress failed |
| pk.started | PK battle started |
| pk.score_update | PK score updated |
| pk.ended | PK battle ended |
| pk.cancelled | PK battle cancelled |
BaxVerify
| Event | Description |
|-------|-------------|
| baxauth.otp.sent | SMS OTP sent |
| baxauth.otp.verified | SMS OTP verified |
| baxauth.otp.failed | SMS OTP failed |
BaxMail
| Event | Description |
|-------|-------------|
| baxmail.sent | Email accepted by provider |
| baxmail.delivered | Email delivered |
| baxmail.failed | Email send/delivery failed |
| baxmail.bounced | Email bounced or marked spam |
| baxmail.deferred | Delivery temporarily deferred |
BaxLinks
| Event | Description |
|-------|-------------|
| link.click | Short link clicked |
| link.install.attributed | Install attributed to a link |
| link.event.install | SDK install event |
| link.event.open | SDK app open event |
| link.event.reinstall | SDK reinstall event |
| link.event.signup | SDK signup event |
| link.event.purchase | SDK purchase event |
| link.event.custom | SDK custom event |
BaxStream
| Event | Description |
|-------|-------------|
| video.conversion.started | HLS conversion started |
| video.conversion.completed | HLS conversion completed |
| video.conversion.failed | HLS conversion failed |
| categorization.started | Standalone AI categorization job began |
| categorization.completed | AI categorization completed |
| categorization.failed | AI categorization failed |
| moderation.started | Standalone AI moderation job began |
| moderation.completed | AI moderation completed |
| moderation.failed | AI moderation failed |
BaxStream Webhook Envelope
Every BaxStream webhook delivery uses the following envelope. The user metadata you
attached to the originating job is hoisted to the top level for fast routing, and
also preserved inside data. eventId is a unique id you can use to dedupe at-least-
once deliveries.
{
"event": "video.conversion.completed",
"eventId": "cm9a8w1xyz...",
"timestamp": "2026-04-17T10:00:00.000Z",
"projectId": "cl_proj_abc123",
"metadata": { "userId": "u_42", "uploadId": "u_42_v17" },
"data": {
"jobId": "cm_job_xyz789",
"projectId": "cl_proj_abc123",
"status": "COMPLETED",
"inputUrl": "https://source.example.com/video.mp4",
"outputUrl": "https://cdn.example.com/videos/master.m3u8",
"thumbnailUrl": "https://cdn.example.com/videos/thumbnail.jpg",
"outputFormats": ["360p", "480p", "720p"],
"renditionUrls": {
"360p": "https://cdn.example.com/videos/360p/playlist.m3u8",
"480p": "https://cdn.example.com/videos/480p/playlist.m3u8",
"720p": "https://cdn.example.com/videos/720p/playlist.m3u8"
},
"durationSec": 125,
"costCents": 13,
"metadata": { "userId": "u_42", "uploadId": "u_42_v17" },
"createdAt": "2026-04-17T09:58:12.000Z",
"completedAt": "2026-04-17T10:00:00.000Z",
// Present when categorize=true was passed on the job
"categorization": {
"primaryCategory": "Sports",
"confidence": 0.4523,
"categories": [
{ "name": "Sports", "confidence": 0.4523, "primary": true },
{ "name": "Entertainment", "confidence": 0.0812, "primary": false }
],
"tags": ["football", "stadium"],
"scores": { "Sports": 0.4523, "Entertainment": 0.0812 /* ... */ }
},
// Present when moderate=true was passed on the job
"moderation": {
"safe": false,
"violations": [
{ "category": "nudity_explicit", "confidence": 0.5210, "severity": "critical" },
{ "category": "suggestive_nudity", "confidence": 0.3104, "severity": "high" },
{ "category": "lingerie_underwear", "confidence": 0.2850, "severity": "medium" }
],
"scores": { /* full 13-category probability map */ },
"flaggedFrames": [
{ "frameIndex": 2, "violations": [ /* ... */ ] }
]
}
}
}Request headers delivered with every webhook:
| Header | Value |
|--------|-------|
| Content-Type | application/json |
| User-Agent | BaxCloud-VideoConverter/1.0 |
| X-BaxCloud-Event | The event type, e.g. video.conversion.completed |
| X-BaxCloud-Signature | sha256=<hex> — HMAC-SHA256 of the raw body with your webhook secret |
Delivery semantics:
- At-least-once delivery — dedupe on
eventId. - 10 second timeout on every request. Respond with any
2xxto ack; push heavy work to a queue. - Failures (non-2xx, timeouts, network errors) are logged in Dashboard → Webhooks → Deliveries.
Receiving BaxStream Webhooks
import express from 'express';
import { webhookMiddleware } from '@baxcloud/baxcloud-server-sdk';
const app = express();
app.post(
'/webhooks/baxstream',
// Important: raw body is required for signature verification
express.raw({ type: 'application/json' }),
webhookMiddleware(process.env.BAXCLOUD_WEBHOOK_SECRET!),
async (req, res) => {
const ev = req.baxcloudEvent; // verified + parsed
if (await alreadyProcessed(ev.eventId)) return res.status(200).end();
switch (ev.event) {
case 'video.conversion.completed':
await onVideoReady({
jobId: ev.data.jobId,
outputUrl: ev.data.outputUrl,
userId: ev.metadata?.userId,
categorization: ev.data.categorization,
moderation: ev.data.moderation,
});
break;
case 'moderation.completed':
if (!ev.data.safe) await flagForReview(ev.data);
break;
case 'video.conversion.failed':
await onVideoFailed(ev.data.jobId, ev.data.errorMessage);
break;
}
res.status(200).json({ ok: true });
},
);
app.listen(3000);Event Structure
interface BaxCloudWebhookEvent {
event: string; // Event type
projectId: string; // Your project ID
timestamp: string; // ISO 8601 timestamp
room?: {
name: string;
sid?: string;
createdAt?: string;
metadata?: string;
};
participant?: {
identity: string;
sid?: string;
name?: string;
metadata?: string;
};
track?: {
sid?: string;
type?: string;
source?: string;
};
}Security Best Practices
- Always verify webhook signatures - Never process webhooks without verification
- Use HTTPS - Ensure your webhook endpoint uses HTTPS in production
- Keep secrets secure - Store webhook secrets in environment variables
- Validate event data - Always validate event data before processing
- Return 200 quickly - Acknowledge webhooks quickly, process async if needed
TypeScript Support
This SDK is written in TypeScript and includes complete type definitions:
import type {
BaxCloudServerClient,
BaxCloudWebhookEvent,
BaxCloudWebhookEventType,
CreateRoomOptions,
GenerateTokenOptions,
WebhookVerificationOptions,
} from '@baxcloud/baxcloud-server-sdk';Error Handling
try {
const room = await client.createRoom({ name: 'my-room' });
} catch (error) {
if (error.message.includes('timeout')) {
console.error('Request timed out');
} else if (error.message.includes('API error')) {
console.error('API returned an error:', error.message);
} else {
console.error('Unexpected error:', error);
}
}Environment Variables
# Required for API client
BAXCLOUD_API_KEY=your_api_key_here
# Required for webhook verification
BAXCLOUD_WEBHOOK_SECRET=your_webhook_secret_hereRelated SDKs
- @baxcloud/baxstream — docs — video conversion + AI
- @baxcloud/baxmail — docs — transactional email
- @baxcloud/baxverify — docs — SMS OTP verification
- @baxcloud/baxlinks — docs — deep linking & attribution
- @baxcloud/parse-server-baxverify — docs — Parse Server phone auth
- @baxcloud/parse-server-baxmail — docs — Parse Server email adapter
License
MIT
Support
- Help: https://baxcloud.tech/dashboard/help
- Contact: https://baxcloud.tech/contact
- Documentation: https://baxcloud.tech/docs/sdk/server
- Email: [email protected]
