import { eq } from "drizzle-orm"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { db } from "@/lib/db/client"; import { integrations } from "@/lib/db/schema"; import { createLogger } from "@/lib/logger"; import type { WebhookEvent } from "@/lib/services/webhooks"; import { parseEmbyPayload, parseJellyfinPayload, parsePlexPayload, processWebhook, } from "@/lib/services/webhooks"; const log = createLogger("webhooks"); export async function POST( req: NextRequest, { params }: { params: Promise<{ token: string }> }, ) { const { token } = await params; // Look up connection by token — this IS the auth const connection = db .select() .from(integrations) .where(eq(integrations.token, token)) .get(); if (!connection || !connection.enabled) { // Always return 200 to avoid retry storms from media servers return NextResponse.json({ ok: true }); } // Only webhook-type integrations are handled here if (connection.type !== "webhook") { return NextResponse.json({ ok: true }); } const provider = connection.provider as "plex" | "jellyfin" | "emby"; try { let event: WebhookEvent | null; if (provider === "plex") { const formData = await req.formData(); event = parsePlexPayload(formData); } else if (provider === "emby") { const body = await req.json(); event = parseEmbyPayload(body); } else { const body = await req.json(); event = parseJellyfinPayload(body); } if (!event) { // Not a relevant event type — silently ignore return NextResponse.json({ ok: true }); } await processWebhook(connection.id, connection.userId, provider, event); } catch (err) { // Swallow errors — never return non-200 to media servers log.debug("Webhook processing failed:", err); } return NextResponse.json({ ok: true }); }