feat(15-03): fire-and-forget phishing detection on ticket.created webhook

- triggerPhishingDetection() mirrors triggerWorkflowEngine's payload.entity-first shape
- reads createdByContactID (Autotask field) into created_by_contact_id, per entity-mapper.ts:211
- called alongside the existing workflow-engine trigger, not awaited in the request path
This commit is contained in:
lorentz 2026-07-15 07:48:17 -04:00
parent dbd2ebe63c
commit 194b58b196

View file

@ -14,6 +14,7 @@ import { workflowEngine } from './workflow-engine';
import { ticketWorkflowEngine } from './ticket-workflow-engine';
import '../services/workflow-steps'; // Register all workflow step executors
import { WorkflowEvent, TicketData } from '../types/workflow';
import { detectPhishingTicket, DetectableTicket } from './phishing-detector';
export class WebhookService {
private _autotaskClient: AutotaskClient | null = null;
@ -114,6 +115,9 @@ export class WebhookService {
this.triggerWorkflowEngine(payload).catch(err =>
console.error('[WEBHOOK] Workflow engine error:', err)
);
this.triggerPhishingDetection(payload).catch(err =>
console.error('[WEBHOOK] Phishing detection error:', err)
);
}
@ -436,6 +440,45 @@ export class WebhookService {
// DEPRECATED: old workflow engine (will be removed after testing period)
// await workflowEngine.process(event);
}
/**
* Trigger phishing detection for a new ticket.
* Runs asynchronously does not block webhook response. Mirrors
* triggerWorkflowEngine's "prefer inline payload.entity, else payload.entityId" shape.
*/
private async triggerPhishingDetection(payload: AutotaskWebhookPayload): Promise<void> {
let ticket: DetectableTicket;
if (payload.entity) {
// NOTE: Autotask's requester field is `createdByContactID` (mapped to
// created_by_contact_id at lib/utils/entity-mapper.ts:211). There is no
// similarly-named alternative field — since payload.entity is typed
// Record<string, any>, a wrong field name would compile cleanly but
// silently produce undefined at runtime.
ticket = {
id: payload.entityId,
ticket_number: payload.entity.ticketNumber || null,
title: payload.entity.title || null,
description: payload.entity.description || null,
company_id: payload.entity.companyID || null,
contact_id: payload.entity.contactID || null,
created_by_contact_id: payload.entity.createdByContactID || null,
};
} else {
ticket = {
id: payload.entityId,
ticket_number: null,
title: null,
description: null,
company_id: null,
contact_id: null,
created_by_contact_id: null,
};
}
console.log(`[WEBHOOK] Triggering phishing detection for ticket ${payload.entityId}`);
await detectPhishingTicket(ticket);
}
}
// Export singleton instance