/** * Autotask Webhook Receiver Endpoint * Receives and processes real-time webhook events from Autotask * * Actual Autotask payload format: * { Action, Guid, EntityType (singular), Id, Fields, EventTime, SequenceNumber, PersonId } * Signature header: x-hook-signature: sha1= */ import { NextRequest, NextResponse } from 'next/server'; import { webhookService } from '@/lib/services/webhook-service'; import { AutotaskRawWebhookPayload, normalizeWebhookPayload } from '@/lib/types/webhook'; /** * POST /api/webhooks/autotask * Receives webhook events from Autotask */ export async function POST(request: NextRequest) { try { // Extract source IP — Autotask sends from 8.34.161.x via Cloudflare/Pangolin const sourceIp = request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for')?.split(',')[0].trim() || request.headers.get('x-real-ip') || 'unknown'; const userAgent = request.headers.get('user-agent') || 'unknown'; // Read raw body for signature verification const rawBody = await request.text(); // Verify webhook signature (x-hook-signature: sha1=) const signatureHeader = request.headers.get('x-hook-signature'); if (!webhookService.verifySignature(rawBody, signatureHeader)) { console.warn(`[WEBHOOK API] Invalid signature from IP: ${sourceIp}`); return NextResponse.json( { error: 'Invalid webhook signature' }, { status: 401 } ); } // Parse raw Autotask payload const rawPayload: AutotaskRawWebhookPayload = JSON.parse(rawBody); // Validate required fields if (!rawPayload.Guid || !rawPayload.Action || !rawPayload.EntityType || !rawPayload.Id) { console.warn(`[WEBHOOK API] Invalid payload from IP: ${sourceIp}`, rawBody.substring(0, 200)); return NextResponse.json( { error: 'Invalid webhook payload: missing required fields (Guid, Action, EntityType, Id)' }, { status: 400 } ); } // Normalize to our internal format const payload = normalizeWebhookPayload(rawPayload); console.log(`[WEBHOOK API] Received ${rawPayload.Action} event for ${rawPayload.EntityType} #${rawPayload.Id} from IP: ${sourceIp}`); // Process the webhook — return 200 quickly to avoid Autotask timeouts const result = await webhookService.processWebhook(payload, sourceIp, userAgent); if (result.success) { return NextResponse.json({ success: true, eventId: result.eventId, action: result.action, processingTime: result.processingTime, }); } else { // Return 200 even on processing failure to prevent Autotask retries/deactivation console.error(`[WEBHOOK API] Processing failed: ${result.error}`); return NextResponse.json({ success: false, eventId: result.eventId, error: result.error, }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('[WEBHOOK API] Error processing webhook:', errorMessage); // Return 200 to prevent Autotask from deactivating the webhook on errors return NextResponse.json( { error: 'Internal server error', details: errorMessage }, { status: 200 } ); } } /** * GET /api/webhooks/autotask * Health check endpoint for webhook receiver */ export async function GET() { return NextResponse.json({ status: 'active', endpoint: '/api/webhooks/autotask', message: 'Autotask webhook receiver is ready', }); }