Files
twenty/packages/twenty-apps/community/fireflies/src/webhook-validator.ts
T
Alex Galey b578ea5951 Fireflies app reached beta : utilities to ingest one or many meetings - No production webhook without headers forwarding (#16410)
# Fireflies
Automatically (with cli util : webhook not working and no cron added)
captures meeting notes with AI-generated summaries and insights from
Fireflies.ai into your Twenty CRM.

## Current Status
- Doesn't work with Fireflies webhook yet due to missing headers
forwarding in twenty serverless func
- Meeting ingestion utility script are available for individual meeting
insertion and historical meetings with filters with yarn meeting:all
- You have to push the secrets as application.config.ts values (despite
env variables in .env in docker compose or container I couldn't push the
secrets with the cli)

## Current Platform Limitation (Headers)

- Twenty serverless route triggers currently do **not forward HTTP
headers** to functions. Fireflies signatures sent in headers are
stripped, so header-based verification does not work in production.
- Workaround: the provided test script also includes the signature
inside the payload; the handler falls back to that payload signature.
Use this only for testing until header forwarding is supported.

## Utilities for meeting insertion (workarounds)

- Ingest a specific Fireflies meeting into Twenty:
`yarn meeting:ingest <meetingId>` or `MEETING_ID=... yarn
meeting:ingest`

- Fetch all/historical Fireflies meetings into Twenty:
`yarn meeting:all [--from 2024-01-01] [--to 2024-02-01] [--organizer
a@x.com] [--participant b@x.com] [--channel <channelId>] [--mine]
[--dry-run]`

  - Filters (combine as needed):
    - `--from` / `--to`: ISO or date string range filter
    - `--organizer` / `--participant`: comma-separated emails
    - `--channel`: Fireflies channel id
    - `--mine`: only meetings for the current Fireflies user
  - Controls:
    - `--dry-run`: list and transform without writing to Twenty
    - `--page-size`: pagination size (default 50)
    - `--max-records`: stop after N transcripts (default 500)
    
    
    I am closing previous #16378  as this one includes it all
2025-12-09 09:27:01 +01:00

55 lines
1.4 KiB
TypeScript

import { createHash, createHmac } from 'crypto';
import type { FirefliesWebhookPayload } from './types';
export type SignatureVerificationResult = {
isValid: boolean;
computedSignature?: string;
};
export const verifyWebhookSignature = (
body: string,
signature: string | undefined,
secret: string
): SignatureVerificationResult => {
if (!signature) {
return { isValid: false };
}
try {
const hmac = createHmac('sha256', secret);
hmac.update(body, 'utf8');
const computed = hmac.digest('hex');
const computedSignature = `sha256=${computed}`;
const isValid = signature === computedSignature;
return { isValid, computedSignature };
} catch {
return { isValid: false };
}
};
export const getWebhookSecretFingerprint = (secret: string): string => {
return createHash('sha256').update(secret).digest('hex').substring(0, 8);
};
export const isValidFirefliesPayload = (
params: unknown
): params is FirefliesWebhookPayload => {
if (!params || typeof params !== 'object') {
return false;
}
const payload = params as Record<string, unknown>;
return (
typeof payload['meetingId'] === 'string' &&
payload['meetingId'].length > 0 &&
typeof payload['eventType'] === 'string' &&
payload['eventType'].length > 0 &&
(payload['clientReferenceId'] === undefined ||
typeof payload['clientReferenceId'] === 'string')
);
};