diff --git a/lib/services/webhook-service.ts b/lib/services/webhook-service.ts index d650ba9..bf7ff67 100644 --- a/lib/services/webhook-service.ts +++ b/lib/services/webhook-service.ts @@ -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 { - 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, 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); }