nice-room-70150
02/21/2026, 9:02 PMtall-airline-16756
02/21/2026, 9:02 PMtall-airline-16756
02/21/2026, 9:02 PMnice-room-70150
02/21/2026, 9:03 PMtall-airline-16756
02/21/2026, 9:03 PMWebhookEvent body and an Authorization header containing a signed JWT. Your endpoint should validate this signature.
2) Secure and validate incoming webhooks
• Validate the JWT in the Authorization header to ensure requests are from LiveKit.
• Ensure idempotency: each WebhookEvent has a unique id. Use this to dedupe events in case of retries.
3) Example: Forward LiveKit webhooks to PostHog (Node.js / Express)
• This example:
◦ Validates the webhook (using your existing validation method).
◦ Maps LiveKit event fields to a PostHog event payload.
◦ Uses the PostHog HTTP API to capture events.
// Example: express app that receives LiveKit webhooks and forwards to PostHog
const express = require('express');
const fetch = require('node-fetch'); // or global fetch in newer Node
const bodyParser = require('body-parser');
const POSTHOG_API_URL = '<https://app.posthog.com>'; // or your self-hosted URL
const POSTHOG_API_KEY = process.env.POSTHOG_API_KEY; // project API key
const PORT = process.env.PORT || 3000;
const app = express();
// LiveKit sends webhook with content-type application/webhook+json
app.use('/livekit-webhook', bodyParser.raw({ type: ['application/webhook+json', 'application/json'] }));
<http://app.post|app.post>('/livekit-webhook', async (req, res) => {
try {
const authHeader = req.get('Authorization') || '';
// Validate signature/JWT here (replace with your validation function)
const isValid = validateLiveKitWebhook(req.body, authHeader);
if (!isValid) return res.status(401).json({ error: 'Invalid signature' });
const event = JSON.parse(req.body.toString('utf8'));
// Use event.id to ensure idempotency in your storage if needed
// Construct a PostHog event
const posthogEvent = {
event: event.event, // e.g., "participant_joined"
properties: {
livekit_event_id: event.id,
room_sid: event.room?.sid,
room_name: event.room?.name,
participant_sid: event.participant?.sid,
participant_identity: event.participant?.identity,
timestamp: event.ts,
// include any other fields of interest
payload: event
},
distinct_id: event.participant?.sid || event.room?.sid || 'unknown'
};
// Send to PostHog capture API
await fetch(`${POSTHOG_API_URL}/capture/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${POSTHOG_API_KEY}`
},
body: JSON.stringify(posthogEvent)
});
res.json({ success: true });
} catch (err) {
console.error('Webhook forward error', err);
res.status(500).json({ error: 'internal_error' });
}
});
// Placeholder: implement actual LiveKit webhook JWT validation
function validateLiveKitWebhook(rawBody, authHeader) {
// Validate the JWT signature in authHeader and return true/false.
// Use LiveKit SDK or your configured signing key to verify.
return true;
}
app.listen(PORT, () => console.log(`Listening on ${PORT}`));
Notes about the example:
• Replace validateLiveKitWebhook with real verification using your LiveKit signing key. The Authorization header contains a signed JWT.
• Use the PostHog project API key (or ingestion key) for the capture endpoint. For self-hosted PostHog the base URL may differ.
• Use distinct_id to identify users/sessions in PostHog. Choose participant_sid, user ID, or room_sid depending on your analytics needs.
• Store processed webhook IDs to prevent duplicate events from retries.
4) Helpful mapping suggestions
• Map event types to PostHog event names directly (e.g., LiveKit participant_joined → PostHog event participant_joined).
• Include common properties:
◦ room: { sid, name }
◦ participant: { sid, identity }
◦ track info: { kind, sid, name }
◦ timestamps and any quality metrics present in the webhook payload
• If you need aggregated metrics (e.g., minutes per room), either:
◦ send additional summary events from your backend, or
◦ compute aggregates in PostHog using their cohorts/insights or via periodic batch events.
5) Verification and testing
• Start with a staging webhook endpoint. Trigger events by creating/joining a test room.
• Check your backend logs for receipt and validation of requests.
• Confirm events appear in PostHog:
◦ Use the Events/Insights page and filter by event name or livekit_event_id.
• Verify no duplicates by checking unique webhook IDs you stored.
6) Advanced considerations
• If you need media-quality metrics from the SFU (bitrate, jitter, packet loss), webhooks may include some lifecycle info but not all per-second metrics. For higher-fidelity telemetry consider combining webhooks with LiveKit telemetry/OpenTelemetry exports.
• If you run LiveKit Agents, you can also forward agent logs/transcripts via the same pattern (receive, transform, post to PostHog).
• Respect PII: Avoid sending raw sensitive data to PostHog unless allowed by your privacy rules.
If you’d like, I can:
• Provide a complete sample for validating LiveKit webhook JWTs (tell me what language you prefer).
• Customize the mapping between LiveKit fields and PostHog properties for the specific events you care about.
• Add retry/deduplication example code using a simple Redis store.
Which of those would you like next?nice-room-70150
02/21/2026, 9:06 PMtall-airline-16756
02/21/2026, 9:07 PM