From 0636989d6713659cc5a548c55a22322453c5268c Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:03:17 +0530 Subject: [PATCH 1/2] fix: update test configuration to exclude .next directory from testing (#7334) --- apps/web/vite.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/vite.config.mts b/apps/web/vite.config.mts index d116d321ace7..bb38b392b405 100644 --- a/apps/web/vite.config.mts +++ b/apps/web/vite.config.mts @@ -8,7 +8,7 @@ export default defineConfig({ test: { environment: "node", environmentMatchGlobs: [["**/*.test.tsx", "jsdom"]], - exclude: ["playwright/**", "node_modules/**"], + exclude: ["playwright/**", "node_modules/**", ".next/**"], setupFiles: ["./vitestSetup.ts"], env: loadEnv("", process.cwd(), ""), coverage: { From 7cea53130c868c2e90c44df2c75798f3af1627d0 Mon Sep 17 00:00:00 2001 From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:36:50 +0400 Subject: [PATCH 2/2] chore: adds webhook signing to test event (#7320) --- .../modules/integrations/webhooks/actions.ts | 46 +++++++++++++++++-- .../webhooks/components/add-webhook-modal.tsx | 25 +++++++--- .../components/webhook-settings-tab.tsx | 13 ++++-- .../integrations/webhooks/lib/webhook.ts | 36 +++++++++------ 4 files changed, 92 insertions(+), 28 deletions(-) diff --git a/apps/web/modules/integrations/webhooks/actions.ts b/apps/web/modules/integrations/webhooks/actions.ts index ece0c2c089ec..c1c4185c9261 100644 --- a/apps/web/modules/integrations/webhooks/actions.ts +++ b/apps/web/modules/integrations/webhooks/actions.ts @@ -2,6 +2,8 @@ import { z } from "zod"; import { ZId } from "@formbricks/types/common"; +import { ResourceNotFoundError } from "@formbricks/types/errors"; +import { generateWebhookSecret } from "@/lib/crypto"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; @@ -24,6 +26,7 @@ import { ZWebhookInput } from "@/modules/integrations/webhooks/types/webhooks"; const ZCreateWebhookAction = z.object({ environmentId: ZId, webhookInput: ZWebhookInput, + webhookSecret: z.string().optional(), }); export const createWebhookAction = authenticatedActionClient.schema(ZCreateWebhookAction).action( @@ -47,7 +50,11 @@ export const createWebhookAction = authenticatedActionClient.schema(ZCreateWebho }, ], }); - const webhook = await createWebhook(parsedInput.environmentId, parsedInput.webhookInput); + const webhook = await createWebhook( + parsedInput.environmentId, + parsedInput.webhookInput, + parsedInput.webhookSecret + ); ctx.auditLoggingCtx.organizationId = organizationId; ctx.auditLoggingCtx.newObject = parsedInput.webhookInput; return webhook; @@ -131,10 +138,43 @@ export const updateWebhookAction = authenticatedActionClient.schema(ZUpdateWebho const ZTestEndpointAction = z.object({ url: z.string(), + webhookId: ZId.optional(), + secret: z.string().optional(), }); export const testEndpointAction = authenticatedActionClient .schema(ZTestEndpointAction) - .action(async ({ parsedInput }) => { - return testEndpoint(parsedInput.url); + .action(async ({ ctx, parsedInput }) => { + let secret: string | undefined; + + if (parsedInput.webhookId) { + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId: await getOrganizationIdFromWebhookId(parsedInput.webhookId), + access: [ + { + type: "organization", + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + minPermission: "read", + projectId: await getProjectIdFromWebhookId(parsedInput.webhookId), + }, + ], + }); + + const webhookResult = await getWebhook(parsedInput.webhookId); + if (!webhookResult.ok) { + throw new ResourceNotFoundError("Webhook", parsedInput.webhookId); + } + + secret = webhookResult.data.secret ?? undefined; + } else { + // New webhook, use the provided secret or generate a new one + secret = parsedInput.secret ?? generateWebhookSecret(); + } + + await testEndpoint(parsedInput.url, secret); + return { success: true, secret }; }); diff --git a/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx b/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx index e1ff5382dfcb..4247a0f2f61c 100644 --- a/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx +++ b/apps/web/modules/integrations/webhooks/components/add-webhook-modal.tsx @@ -53,16 +53,22 @@ export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWe const [selectedAllSurveys, setSelectedAllSurveys] = useState(false); const [creatingWebhook, setCreatingWebhook] = useState(false); const [createdWebhook, setCreatedWebhook] = useState(null); + const [webhookSecret, setWebhookSecret] = useState(); - const handleTestEndpoint = async (sendSuccessToast: boolean) => { + const handleTestEndpoint = async ( + sendSuccessToast: boolean + ): Promise<{ success: boolean; secret?: string }> => { try { const { valid, error } = validWebHookURL(testEndpointInput); if (!valid) { toast.error(error ?? t("common.something_went_wrong_please_try_again")); - return; + return { success: false }; } setHittingEndpoint(true); - const testEndpointActionResult = await testEndpointAction({ url: testEndpointInput }); + const testEndpointActionResult = await testEndpointAction({ + url: testEndpointInput, + secret: webhookSecret, + }); if (!testEndpointActionResult?.data) { const errorMessage = getFormattedErrorMessage(testEndpointActionResult); throw new Error(errorMessage); @@ -70,7 +76,10 @@ export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWe setHittingEndpoint(false); if (sendSuccessToast) toast.success(t("environments.integrations.webhooks.endpoint_pinged")); setEndpointAccessible(true); - return true; + if (testEndpointActionResult.data.secret) { + setWebhookSecret(testEndpointActionResult.data.secret); + } + return testEndpointActionResult.data; } catch (err) { setHittingEndpoint(false); toast.error( @@ -83,7 +92,7 @@ export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWe ); console.error(t("environments.integrations.webhooks.webhook_test_failed_due_to"), err.message); setEndpointAccessible(false); - return false; + return { success: false }; } }; @@ -127,8 +136,8 @@ export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWe throw new Error(t("environments.integrations.webhooks.discord_webhook_not_supported")); } - const endpointHitSuccessfully = await handleTestEndpoint(false); - if (!endpointHitSuccessfully) return; + const testResult = await handleTestEndpoint(false); + if (!testResult.success) return; const updatedData: TWebhookInput = { name: data.name, @@ -141,6 +150,7 @@ export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWe const createWebhookActionResult = await createWebhookAction({ environmentId, webhookInput: updatedData, + webhookSecret: testResult.secret, }); if (createWebhookActionResult?.data) { router.refresh(); @@ -167,6 +177,7 @@ export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWe setSelectedTriggers([]); setSelectedAllSurveys(false); setCreatedWebhook(null); + setWebhookSecret(undefined); }; // Show success dialog with secret after webhook creation diff --git a/apps/web/modules/integrations/webhooks/components/webhook-settings-tab.tsx b/apps/web/modules/integrations/webhooks/components/webhook-settings-tab.tsx index 1e93c069ba96..41dc8c4f5fff 100644 --- a/apps/web/modules/integrations/webhooks/components/webhook-settings-tab.tsx +++ b/apps/web/modules/integrations/webhooks/components/webhook-settings-tab.tsx @@ -58,16 +58,19 @@ export const WebhookSettingsTab = ({ webhook, surveys, setOpen, isReadOnly }: We setTimeout(() => setCopied(false), 2000); }; - const handleTestEndpoint = async (sendSuccessToast: boolean) => { + const handleTestEndpoint = async (sendSuccessToast: boolean): Promise => { try { const { valid, error } = validWebHookURL(testEndpointInput); if (!valid) { toast.error(error ?? t("common.something_went_wrong_please_try_again")); - return; + return false; } setHittingEndpoint(true); - const testEndpointActionResult = await testEndpointAction({ url: testEndpointInput }); - if (!testEndpointActionResult?.data) { + const testEndpointActionResult = await testEndpointAction({ + url: testEndpointInput, + webhookId: webhook.id, + }); + if (!testEndpointActionResult?.data?.success) { const errorMessage = getFormattedErrorMessage(testEndpointActionResult); throw new Error(errorMessage); } @@ -220,7 +223,7 @@ export const WebhookSettingsTab = ({ webhook, surveys, setOpen, isReadOnly }: We />