next.jswebhooksngrokclerk

Clerk and Svix web hooks not working with error: "src property must be a valid json object"


I'm trying to sync my Clerk data to my database in my Next js 13 project. My webhooks are publicly exposed with Ngrok. Here's my code:

import { IncomingHttpHeaders } from "http";
import { headers } from "next/headers";
import { NextResponse } from "next/server";
import { Webhook, WebhookRequiredHeaders } from "svix";

const webhookSecret = process.env.WEBHOOK_SECRET || "";

async function handler(request: Request) {

  console.log(await request.json())

  const payload = await request.json();
  const headersList = headers();
  const heads = {
    "svix-id": headersList.get("svix-id"),
    "svix-timestamp": headersList.get("svix-timestamp"),
    "svix-signature": headersList.get("svix-signature"),
  };
  const wh = new Webhook(webhookSecret);
  let evt: Event | null = null;

  try {
    evt = wh.verify(
      JSON.stringify(payload),
      heads as IncomingHttpHeaders & WebhookRequiredHeaders
    ) as Event;
  } catch (err) {
    console.error((err as Error).message);
    return NextResponse.json({}, { status: 400 });
  }

  const eventType: EventType = evt.type;
  if (eventType === "user.created" || eventType === "user.updated") {
    const { id, ...attributes } = evt.data;
    console.log(attributes)
  }
}

type EventType = "user.created" | "user.updated" | "*";

type Event = {
  data: Record<string, string | number>;
  object: "event";
  type: EventType;
};

export const GET = handler;
export const POST = handler;
export const PUT = handler;

This code should do the following:

  1. Create an API route under /api/webhooks/user
  2. Fetch the payload and headers
  3. Validate this info with Svix
  4. Console the information

However, only step 1 is working as far as I can tell. In my Clerk dashboard I get an error:

{
  "message": "src property must be a valid json object"
}

Edit:

With the following code I'm still getting the same error:

import { Webhook, WebhookRequiredHeaders } from "svix";

const webhookSecret = process.env.WEBHOOK_SECRET || "";

async function handler(request: Request) {
    const svix_id = request.headers.get("svix-id") ?? "";
    const svix_timestamp = request.headers.get("svix-timestamp") ?? "";
    const svix_signature = request.headers.get("svix-signature") ?? "";

    const body = await request.text(); // This get's the raw body as a string

    const sivx = new Webhook("your_secret_key_here");

    const payload = sivx.verify(body, {
        "svix-id": svix_id,
        "svix-timestamp": svix_timestamp,
        "svix-signature": svix_signature,
    });

    console.log(payload)
}

export const GET = handler;
export const POST = handler;
export const PUT = handler;

Where am I going wrong?


Solution

  • You need to add your webhook endpoint to your ignoredRoutes in middleware.ts:

    export default authMiddleware({
      publicRoutes: ['/'],
      ignoredRoutes: ['/api/{routeName}'],
    });
    

    There's annoyingly little documentation to highlight this though.