From 53fcad67fae8263020b0fb6bfa41da1a36b3c516 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Tue, 11 Nov 2025 21:17:51 -0800 Subject: [PATCH 1/3] Add COMMON_CORS_HEADERS in /track/client error responses --- .../app/(ee)/api/track/lead/client/route.ts | 78 +++++++------- .../app/(ee)/api/track/sale/client/route.ts | 102 +++++++++--------- 2 files changed, 94 insertions(+), 86 deletions(-) diff --git a/apps/web/app/(ee)/api/track/lead/client/route.ts b/apps/web/app/(ee)/api/track/lead/client/route.ts index 03ad1093c6f..2bce38e5ec4 100644 --- a/apps/web/app/(ee)/api/track/lead/client/route.ts +++ b/apps/web/app/(ee)/api/track/lead/client/route.ts @@ -4,7 +4,7 @@ import { } from "@/lib/analytics/verify-analytics-allowed-hostnames"; import { trackLead } from "@/lib/api/conversions/track-lead"; import { COMMON_CORS_HEADERS } from "@/lib/api/cors"; -import { DubApiError } from "@/lib/api/errors"; +import { DubApiError, handleAndReturnErrorResponse } from "@/lib/api/errors"; import { parseRequestBody } from "@/lib/api/utils"; import { withPublishableKey } from "@/lib/auth/publishable-key"; import { trackLeadRequestSchema } from "@/lib/zod/schemas/leads"; @@ -13,47 +13,51 @@ import { NextResponse } from "next/server"; // POST /api/track/lead/client – Track a lead conversion event on the client side export const POST = withPublishableKey( async ({ req, workspace }) => { - const body = await parseRequestBody(req); + try { + const body = await parseRequestBody(req); - const allowRequest = verifyAnalyticsAllowedHostnames({ - allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], - req, - }); - - if (!allowRequest) { - throw new DubApiError({ - code: "forbidden", - message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, + const allowRequest = verifyAnalyticsAllowedHostnames({ + allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], + req, }); - } - const { - clickId, - eventName, - eventQuantity, - customerExternalId, - customerName, - customerEmail, - customerAvatar, - mode, - metadata, - } = trackLeadRequestSchema.parse(body); + if (!allowRequest) { + throw new DubApiError({ + code: "forbidden", + message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, + }); + } - const response = await trackLead({ - clickId, - eventName, - eventQuantity, - customerExternalId, - customerName, - customerEmail, - customerAvatar, - mode, - metadata, - rawBody: body, - workspace, - }); + const { + clickId, + eventName, + eventQuantity, + customerExternalId, + customerName, + customerEmail, + customerAvatar, + mode, + metadata, + } = trackLeadRequestSchema.parse(body); - return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); + const response = await trackLead({ + clickId, + eventName, + eventQuantity, + customerExternalId, + customerName, + customerEmail, + customerAvatar, + mode, + metadata, + rawBody: body, + workspace, + }); + + return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); + } catch (error) { + return handleAndReturnErrorResponse(error, COMMON_CORS_HEADERS); + } }, { requiredPlan: [ diff --git a/apps/web/app/(ee)/api/track/sale/client/route.ts b/apps/web/app/(ee)/api/track/sale/client/route.ts index 82f0ea0c844..5a2ec79b917 100644 --- a/apps/web/app/(ee)/api/track/sale/client/route.ts +++ b/apps/web/app/(ee)/api/track/sale/client/route.ts @@ -4,7 +4,7 @@ import { } from "@/lib/analytics/verify-analytics-allowed-hostnames"; import { trackSale } from "@/lib/api/conversions/track-sale"; import { COMMON_CORS_HEADERS } from "@/lib/api/cors"; -import { DubApiError } from "@/lib/api/errors"; +import { DubApiError, handleAndReturnErrorResponse } from "@/lib/api/errors"; import { parseRequestBody } from "@/lib/api/utils"; import { withPublishableKey } from "@/lib/auth/publishable-key"; import { trackSaleRequestSchema } from "@/lib/zod/schemas/sales"; @@ -13,60 +13,64 @@ import { NextResponse } from "next/server"; // POST /api/track/sale/client – Track a sale conversion event on the client side export const POST = withPublishableKey( async ({ req, workspace }) => { - const body = await parseRequestBody(req); + try { + const body = await parseRequestBody(req); - const allowRequest = verifyAnalyticsAllowedHostnames({ - allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], - req, - }); - - if (!allowRequest) { - throw new DubApiError({ - code: "forbidden", - message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, + const allowRequest = verifyAnalyticsAllowedHostnames({ + allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], + req, }); - } - const { - customerExternalId, - customerName, - customerEmail, - customerAvatar, - clickId, - amount, - currency, - eventName, - paymentProcessor, - invoiceId, - leadEventName, - metadata, - } = trackSaleRequestSchema.parse(body); + if (!allowRequest) { + throw new DubApiError({ + code: "forbidden", + message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, + }); + } - if (!customerExternalId) { - throw new DubApiError({ - code: "bad_request", - message: "customerExternalId is required", - }); - } + const { + customerExternalId, + customerName, + customerEmail, + customerAvatar, + clickId, + amount, + currency, + eventName, + paymentProcessor, + invoiceId, + leadEventName, + metadata, + } = trackSaleRequestSchema.parse(body); - const response = await trackSale({ - customerExternalId, - customerName, - customerEmail, - customerAvatar, - clickId, - amount, - currency, - eventName, - paymentProcessor, - invoiceId, - leadEventName, - metadata, - workspace, - rawBody: body, - }); + if (!customerExternalId) { + throw new DubApiError({ + code: "bad_request", + message: "customerExternalId is required", + }); + } - return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); + const response = await trackSale({ + customerExternalId, + customerName, + customerEmail, + customerAvatar, + clickId, + amount, + currency, + eventName, + paymentProcessor, + invoiceId, + leadEventName, + metadata, + workspace, + rawBody: body, + }); + + return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); + } catch (error) { + return handleAndReturnErrorResponse(error, COMMON_CORS_HEADERS); + } }, { requiredPlan: [ From 4b642723a5a4801fe41b0413934c76f2c154e72b Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Tue, 11 Nov 2025 21:25:08 -0800 Subject: [PATCH 2/3] set COMMON_CORS_HEADERS in withPublishableKey --- .../app/(ee)/api/track/lead/client/route.ts | 78 +++++++------- .../app/(ee)/api/track/sale/client/route.ts | 102 +++++++++--------- apps/web/lib/auth/publishable-key.ts | 3 +- 3 files changed, 88 insertions(+), 95 deletions(-) diff --git a/apps/web/app/(ee)/api/track/lead/client/route.ts b/apps/web/app/(ee)/api/track/lead/client/route.ts index 2bce38e5ec4..03ad1093c6f 100644 --- a/apps/web/app/(ee)/api/track/lead/client/route.ts +++ b/apps/web/app/(ee)/api/track/lead/client/route.ts @@ -4,7 +4,7 @@ import { } from "@/lib/analytics/verify-analytics-allowed-hostnames"; import { trackLead } from "@/lib/api/conversions/track-lead"; import { COMMON_CORS_HEADERS } from "@/lib/api/cors"; -import { DubApiError, handleAndReturnErrorResponse } from "@/lib/api/errors"; +import { DubApiError } from "@/lib/api/errors"; import { parseRequestBody } from "@/lib/api/utils"; import { withPublishableKey } from "@/lib/auth/publishable-key"; import { trackLeadRequestSchema } from "@/lib/zod/schemas/leads"; @@ -13,51 +13,47 @@ import { NextResponse } from "next/server"; // POST /api/track/lead/client – Track a lead conversion event on the client side export const POST = withPublishableKey( async ({ req, workspace }) => { - try { - const body = await parseRequestBody(req); + const body = await parseRequestBody(req); - const allowRequest = verifyAnalyticsAllowedHostnames({ - allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], - req, + const allowRequest = verifyAnalyticsAllowedHostnames({ + allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], + req, + }); + + if (!allowRequest) { + throw new DubApiError({ + code: "forbidden", + message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, }); + } - if (!allowRequest) { - throw new DubApiError({ - code: "forbidden", - message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, - }); - } + const { + clickId, + eventName, + eventQuantity, + customerExternalId, + customerName, + customerEmail, + customerAvatar, + mode, + metadata, + } = trackLeadRequestSchema.parse(body); - const { - clickId, - eventName, - eventQuantity, - customerExternalId, - customerName, - customerEmail, - customerAvatar, - mode, - metadata, - } = trackLeadRequestSchema.parse(body); + const response = await trackLead({ + clickId, + eventName, + eventQuantity, + customerExternalId, + customerName, + customerEmail, + customerAvatar, + mode, + metadata, + rawBody: body, + workspace, + }); - const response = await trackLead({ - clickId, - eventName, - eventQuantity, - customerExternalId, - customerName, - customerEmail, - customerAvatar, - mode, - metadata, - rawBody: body, - workspace, - }); - - return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); - } catch (error) { - return handleAndReturnErrorResponse(error, COMMON_CORS_HEADERS); - } + return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); }, { requiredPlan: [ diff --git a/apps/web/app/(ee)/api/track/sale/client/route.ts b/apps/web/app/(ee)/api/track/sale/client/route.ts index 5a2ec79b917..82f0ea0c844 100644 --- a/apps/web/app/(ee)/api/track/sale/client/route.ts +++ b/apps/web/app/(ee)/api/track/sale/client/route.ts @@ -4,7 +4,7 @@ import { } from "@/lib/analytics/verify-analytics-allowed-hostnames"; import { trackSale } from "@/lib/api/conversions/track-sale"; import { COMMON_CORS_HEADERS } from "@/lib/api/cors"; -import { DubApiError, handleAndReturnErrorResponse } from "@/lib/api/errors"; +import { DubApiError } from "@/lib/api/errors"; import { parseRequestBody } from "@/lib/api/utils"; import { withPublishableKey } from "@/lib/auth/publishable-key"; import { trackSaleRequestSchema } from "@/lib/zod/schemas/sales"; @@ -13,64 +13,60 @@ import { NextResponse } from "next/server"; // POST /api/track/sale/client – Track a sale conversion event on the client side export const POST = withPublishableKey( async ({ req, workspace }) => { - try { - const body = await parseRequestBody(req); + const body = await parseRequestBody(req); - const allowRequest = verifyAnalyticsAllowedHostnames({ - allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], - req, - }); - - if (!allowRequest) { - throw new DubApiError({ - code: "forbidden", - message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, - }); - } + const allowRequest = verifyAnalyticsAllowedHostnames({ + allowedHostnames: (workspace?.allowedHostnames ?? []) as string[], + req, + }); - const { - customerExternalId, - customerName, - customerEmail, - customerAvatar, - clickId, - amount, - currency, - eventName, - paymentProcessor, - invoiceId, - leadEventName, - metadata, - } = trackSaleRequestSchema.parse(body); + if (!allowRequest) { + throw new DubApiError({ + code: "forbidden", + message: `Request origin '${getHostnameFromRequest(req)}' is not included in the allowed hostnames for this workspace. Update your allowed hostnames here: https://app.dub.co/settings/analytics`, + }); + } - if (!customerExternalId) { - throw new DubApiError({ - code: "bad_request", - message: "customerExternalId is required", - }); - } + const { + customerExternalId, + customerName, + customerEmail, + customerAvatar, + clickId, + amount, + currency, + eventName, + paymentProcessor, + invoiceId, + leadEventName, + metadata, + } = trackSaleRequestSchema.parse(body); - const response = await trackSale({ - customerExternalId, - customerName, - customerEmail, - customerAvatar, - clickId, - amount, - currency, - eventName, - paymentProcessor, - invoiceId, - leadEventName, - metadata, - workspace, - rawBody: body, + if (!customerExternalId) { + throw new DubApiError({ + code: "bad_request", + message: "customerExternalId is required", }); - - return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); - } catch (error) { - return handleAndReturnErrorResponse(error, COMMON_CORS_HEADERS); } + + const response = await trackSale({ + customerExternalId, + customerName, + customerEmail, + customerAvatar, + clickId, + amount, + currency, + eventName, + paymentProcessor, + invoiceId, + leadEventName, + metadata, + workspace, + rawBody: body, + }); + + return NextResponse.json(response, { headers: COMMON_CORS_HEADERS }); }, { requiredPlan: [ diff --git a/apps/web/lib/auth/publishable-key.ts b/apps/web/lib/auth/publishable-key.ts index dc1f1daf4bb..47fce1a15ea 100644 --- a/apps/web/lib/auth/publishable-key.ts +++ b/apps/web/lib/auth/publishable-key.ts @@ -5,6 +5,7 @@ import { prisma } from "@dub/prisma"; import { getSearchParams } from "@dub/utils"; import { Project } from "@prisma/client"; import { headers } from "next/headers"; +import { COMMON_CORS_HEADERS } from "../api/cors"; interface WithPublishableKeyHandler { ({ @@ -42,7 +43,7 @@ export const withPublishableKey = ( ) => { const params = (await initialParams) || {}; let requestHeaders = await headers(); - let responseHeaders = new Headers(); + let responseHeaders = new Headers(COMMON_CORS_HEADERS); try { const authorizationHeader = requestHeaders.get("Authorization"); From 74f5883e7cf65aa68c232869cf4eec6611d84189 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Tue, 11 Nov 2025 21:28:44 -0800 Subject: [PATCH 3/3] Update publishable-key.ts --- apps/web/lib/auth/publishable-key.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/lib/auth/publishable-key.ts b/apps/web/lib/auth/publishable-key.ts index 47fce1a15ea..7403fa3d889 100644 --- a/apps/web/lib/auth/publishable-key.ts +++ b/apps/web/lib/auth/publishable-key.ts @@ -43,7 +43,7 @@ export const withPublishableKey = ( ) => { const params = (await initialParams) || {}; let requestHeaders = await headers(); - let responseHeaders = new Headers(COMMON_CORS_HEADERS); + let responseHeaders = COMMON_CORS_HEADERS; try { const authorizationHeader = requestHeaders.get("Authorization");