import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as crypto from 'crypto'; export interface PaperlessPayload { document_id?: number; id?: number; title?: string; document_type?: string; correspondent?: string; tags?: string[]; created?: string; added?: string; content?: string; original_file_name?: string; } @Injectable() export class WebhooksService { private readonly logger = new Logger(WebhooksService.name); private readonly secret: string; constructor(private readonly config: ConfigService) { this.secret = config.get('PAPERLESS_WEBHOOK_SECRET', ''); } validateSignature(payload: string, signature: string): boolean { if (!this.secret) return true; // secret not configured → allow (dev mode) const expected = 'sha256=' + crypto .createHmac('sha256', this.secret) .update(payload) .digest('hex'); try { return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); } catch { return false; } } buildObservation(doc: PaperlessPayload) { const docId = doc.document_id ?? doc.id ?? 0; const tags = ['document', 'inbox', ...(doc.tags ?? [])]; if (doc.document_type) tags.push(doc.document_type.toLowerCase().replace(/\s+/g, '-')); return { metric: 'document-received', value: doc.title ?? `Document #${docId}`, unit: doc.document_type ?? 'document', subjectType: 'document', confidence: 1, source: doc.correspondent ?? undefined, observedAt: doc.added ?? doc.created ?? new Date().toISOString(), metadata: { paperlessId: docId, originalFile: doc.original_file_name, tags: doc.tags, excerpt: doc.content?.slice(0, 500), }, }; } logIncoming(type: string, payload: unknown) { this.logger.log(`Webhook received: ${type} — ${JSON.stringify(payload).slice(0, 200)}`); } }