--- name: fireflies-webhooks-events description: 'Implement Fireflies.ai webhook receiver with HMAC signature verification and event processing. Use when setting up webhook endpoints, handling transcript-ready notifications, or building real-time meeting intelligence pipelines. Trigger with phrases like "fireflies webhook", "fireflies events", "fireflies webhook signature", "handle fireflies events", "fireflies notifications". ' allowed-tools: Read, Write, Edit, Bash(curl:*) version: 1.11.0 license: MIT author: Jeremy Longshore tags: - saas - fireflies - webhooks compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw --- # Fireflies.ai Webhooks & Events ## Overview Handle Fireflies.ai webhook events for real-time transcript notifications. Fireflies fires a webhook when a transcript finishes processing. The payload is signed with HMAC-SHA256 for verification. ## Prerequisites - Fireflies.ai Business or Enterprise plan - `FIREFLIES_API_KEY` and `FIREFLIES_WEBHOOK_SECRET` in environment - HTTPS endpoint accessible from the internet ## Webhook Event Reference Fireflies currently fires one event type: | Event | `eventType` Value | Trigger | |-------|-------------------|---------| | Transcription completed | `"Transcription completed"` | Transcript is fully processed and ready | ### Payload Format ```json { "meetingId": "ASxwZxCstx", "eventType": "Transcription completed", "clientReferenceId": "be582c46-4ac9-4565-9ba6-6ab4264496a8" } ``` | Field | Type | Description | |-------|------|-------------| | `meetingId` | String | Transcript ID -- use in `transcript(id:)` query | | `eventType` | String | Always `"Transcription completed"` currently | | `clientReferenceId` | ID | Your custom ID from `uploadAudio` (null if bot-recorded) | ## Important Constraints - Webhooks fire **only for meetings you own** (organizer_email matches your account) - Super Admin webhooks (Enterprise only) fire for all team-owned meetings ## Instructions ### Step 1: Register Webhook in Dashboard 1. Go to [app.fireflies.ai/settings](https://app.fireflies.ai/settings) 2. Select **Developer settings** tab 3. Enter your HTTPS webhook URL 4. Enter or generate a 16-32 character secret 5. Save ### Step 2: Build Webhook Receiver with Signature Verification ```typescript import express from "express"; import crypto from "crypto"; const app = express(); // IMPORTANT: Use raw body for HMAC verification app.post("/webhooks/fireflies", express.raw({ type: "application/json" }), async (req, res) => { const signature = req.headers["x-hub-signature"] as string; const rawBody = req.body.toString(); // Verify HMAC-SHA256 signature if (!signature || !verifySignature(rawBody, signature)) { console.warn("Rejected webhook: invalid signature"); return res.status(401).json({ error: "Invalid signature" }); } // Acknowledge immediately -- process async res.status(200).json({ received: true }); const event = JSON.parse(rawBody); console.log(`Webhook: ${event.eventType} for meeting ${event.meetingId}`); // Process in background processTranscriptReady(event.meetingId, event.clientReferenceId) .catch(err => console.error("Webhook processing failed:", err)); } ); function verifySignature(payload: string, signature: string): boolean { const secret = process.env.FIREFLIES_WEBHOOK_SECRET!; const expected = crypto .createHmac("sha256", secret) .update(payload) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } ``` ### Step 3: Fetch and Process the Transcript ```typescript const FIREFLIES_API = "https://api.fireflies.ai/graphql"; async function processTranscriptReady(meetingId: string, clientRefId?: string) { const res = await fetch(FIREFLIES_API, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.FIREFLIES_API_KEY}`, }, body: JSON.stringify({ query: ` query GetTranscript($id: String!) { transcript(id: $id) { id title date duration organizer_email speakers { name } sentences { speaker_name text start_time end_time } summary { overview action_items keywords short_summary } meeting_attendees { displayName email } } } `, variables: { id: meetingId }, }), }); const json = await res.json(); if (json.errors) throw new Error(json.errors[0].message); const transcript = json.data.transcript; console.log(`Processing: "${transcript.title}" (${transcript.duration}min)`); console.log(`Speakers: ${transcript.speakers.map((s: any) => s.name).join(", ")}`); console.log(`Action items: ${transcript.summary?.action_items?.length || 0}`); // Route to downstream systems await Promise.all([ storeTranscript(transcript), createTasksFromActionItems(transcript), notifyTeam(transcript), ]); } async function storeTranscript(transcript: any) { // Store in your database console.log(`Stored transcript: ${transcript.id}`); } async function createTasksFromActionItems(transcript: any) { const items = transcript.summary?.action_items || []; for (const item of items) { console.log(`Task created: ${item}`); // await taskManager.create({ title: item, source: transcript.title }); } } async function notifyTeam(transcript: any) { // Send Slack/email notification const summary = transcript.summary?.short_summary || transcript.summary?.overview; console.log(`Notification: "${transcript.title}" -- ${summary}`); } ``` ### Step 4: Per-Upload Webhook (Alternative) Instead of dashboard-level webhook, include a webhook URL in `uploadAudio`: ```typescript await fetch(FIREFLIES_API, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.FIREFLIES_API_KEY}`, }, body: JSON.stringify({ query: ` mutation($input: AudioUploadInput) { uploadAudio(input: $input) { success title message } } `, variables: { input: { url: "https://storage.example.com/recording.mp3", title: "Client Call 2026-03-22", webhook: "https://api.yourapp.com/webhooks/fireflies", client_reference_id: "order-12345", }, }, }), }); ``` ### Step 5: Test Webhook ```bash set -euo pipefail # Test by uploading a short audio file curl -s -X POST https://api.fireflies.ai/graphql \ -H "Authorization: Bearer $FIREFLIES_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "mutation($input: AudioUploadInput) { uploadAudio(input: $input) { success message } }", "variables": { "input": { "url": "https://example.com/test-audio.mp3", "title": "Webhook Test" } } }' | jq . # The webhook will fire when transcription completes (usually 2-5 minutes) ``` ## Error Handling | Issue | Cause | Solution | |-------|-------|----------| | Webhook not firing | URL not saved in dashboard | Re-register at app.fireflies.ai/settings | | Invalid signature | Secret mismatch | Verify secret matches dashboard value | | Missing `meetingId` | Malformed payload | Log raw body, check Fireflies status | | Webhook only fires for some meetings | Owner-only constraint | Webhooks fire only for your meetings | | `clientReferenceId` is null | Bot-recorded meeting | Only set on `uploadAudio` calls | ## Output - HTTPS webhook endpoint with HMAC-SHA256 signature verification - Automatic transcript fetch on completion events - Action item extraction and downstream routing - Per-upload webhook support for custom tracking ## Resources - [Fireflies Webhooks](https://docs.fireflies.ai/graphql-api/webhooks) - Webhook Verification Example ## Next Steps For deployment setup, see `fireflies-deploy-integration`.