fix(15): CR-01 fix webhook-triggered phishing detection reading from unpopulated payload.entity

This commit is contained in:
lorentz 2026-07-15 08:05:30 -04:00
parent e94482bd0b
commit ecc34b4bad

View file

@ -443,39 +443,48 @@ export class WebhookService {
/**
* 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.
* Runs asynchronously does not block webhook response.
*
* NOTE: `payload.entity` is never populated by the real Autotask webhook
* flow (see lib/types/webhook.ts's normalizeWebhookPayload) — Autotask's
* actual payload only carries Action/Guid/EntityType/Id/Fields/EventTime,
* with no embedded entity body. Instead, read the ticket row back from
* Postgres by the time this fires, handleCreateOrUpdate() (called
* earlier in the same processWebhook flow, see line ~95) has already
* upserted the ticket, so the row is guaranteed to exist with current
* title/description.
*/
private async triggerPhishingDetection(payload: AutotaskWebhookPayload): Promise<void> {
let ticket: DetectableTicket;
const row = await postgresClient.query<{
id: string;
ticket_number: string | null;
title: string | null;
description: string | null;
company_id: number | null;
contact_id: number | null;
created_by_contact_id: number | null;
}>(
`SELECT id, ticket_number, title, description, company_id, contact_id, created_by_contact_id
FROM tickets WHERE id = $1`,
[payload.entityId]
);
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,
};
const r = row.rows[0];
if (!r) {
console.warn(`[WEBHOOK] Skipping phishing detection — ticket ${payload.entityId} not found in Postgres yet`);
return;
}
const ticket: DetectableTicket = {
id: Number(r.id),
ticket_number: r.ticket_number,
title: r.title,
description: r.description,
company_id: r.company_id,
contact_id: r.contact_id,
created_by_contact_id: r.created_by_contact_id,
};
console.log(`[WEBHOOK] Triggering phishing detection for ticket ${payload.entityId}`);
await detectPhishingTicket(ticket);
}