diff --git a/.env.example b/.env.example
index 2441c96863..f67d8a0e6d 100644
--- a/.env.example
+++ b/.env.example
@@ -189,8 +189,9 @@ REDIS_URL=redis://localhost:6379
# The below is used for Rate Limiting (uses In-Memory LRU Cache if not provided) (You can use a service like Webdis for this)
# REDIS_HTTP_URL:
-# INTERCOM_APP_ID=
-# INTERCOM_SECRET_KEY=
+# Chatwoot
+# CHATWOOT_BASE_URL=
+# CHATWOOT_WEBSITE_TOKEN=
# Enable Prometheus metrics
# PROMETHEUS_ENABLED=
diff --git a/apps/web/app/(app)/layout.tsx b/apps/web/app/(app)/layout.tsx
index 65465c32a4..64b174a46f 100644
--- a/apps/web/app/(app)/layout.tsx
+++ b/apps/web/app/(app)/layout.tsx
@@ -1,5 +1,6 @@
import { getServerSession } from "next-auth";
-import { IntercomClientWrapper } from "@/app/intercom/IntercomClientWrapper";
+import { ChatwootWidget } from "@/app/chatwoot/ChatwootWidget";
+import { CHATWOOT_BASE_URL, CHATWOOT_WEBSITE_TOKEN, IS_CHATWOOT_CONFIGURED } from "@/lib/constants";
import { getUser } from "@/lib/user/service";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { ClientLogout } from "@/modules/ui/components/client-logout";
@@ -18,7 +19,15 @@ const AppLayout = async ({ children }) => {
return (
<>
-
+ {IS_CHATWOOT_CONFIGURED && (
+
+ )}
{children}
>
diff --git a/apps/web/app/(auth)/layout.tsx b/apps/web/app/(auth)/layout.tsx
index ddebf022be..fe322623f6 100644
--- a/apps/web/app/(auth)/layout.tsx
+++ b/apps/web/app/(auth)/layout.tsx
@@ -1,11 +1,9 @@
-import { IntercomClientWrapper } from "@/app/intercom/IntercomClientWrapper";
import { NoMobileOverlay } from "@/modules/ui/components/no-mobile-overlay";
const AppLayout = async ({ children }) => {
return (
<>
-
{children}
>
);
diff --git a/apps/web/app/chatwoot/ChatwootWidget.tsx b/apps/web/app/chatwoot/ChatwootWidget.tsx
new file mode 100644
index 0000000000..c175d373d0
--- /dev/null
+++ b/apps/web/app/chatwoot/ChatwootWidget.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import { useCallback, useEffect, useRef } from "react";
+
+interface ChatwootWidgetProps {
+ chatwootBaseUrl: string;
+ chatwootWebsiteToken?: string;
+ userEmail?: string | null;
+ userName?: string | null;
+ userId?: string | null;
+}
+
+const CHATWOOT_SCRIPT_ID = "chatwoot-script";
+
+export const ChatwootWidget = ({
+ userEmail,
+ userName,
+ userId,
+ chatwootWebsiteToken,
+ chatwootBaseUrl,
+}: ChatwootWidgetProps) => {
+ const userSetRef = useRef(false);
+
+ const setUserInfo = useCallback(() => {
+ const $chatwoot = (
+ globalThis as unknown as {
+ $chatwoot: {
+ setUser: (userId: string, userInfo: { email?: string | null; name?: string | null }) => void;
+ };
+ }
+ ).$chatwoot;
+ if (userId && $chatwoot && !userSetRef.current) {
+ $chatwoot.setUser(userId, {
+ email: userEmail,
+ name: userName,
+ });
+ userSetRef.current = true;
+ }
+ }, [userId, userEmail, userName]);
+
+ useEffect(() => {
+ if (!chatwootWebsiteToken) return;
+
+ const existingScript = document.getElementById(CHATWOOT_SCRIPT_ID);
+ if (existingScript) return;
+
+ const script = document.createElement("script");
+ script.src = `${chatwootBaseUrl}/packs/js/sdk.js`;
+ script.id = CHATWOOT_SCRIPT_ID;
+ script.async = true;
+
+ script.onload = () => {
+ (
+ globalThis as unknown as {
+ chatwootSDK: { run: (options: { websiteToken: string; baseUrl: string }) => void };
+ }
+ ).chatwootSDK?.run({
+ websiteToken: chatwootWebsiteToken,
+ baseUrl: chatwootBaseUrl,
+ });
+ };
+
+ document.head.appendChild(script);
+
+ const handleChatwootReady = () => setUserInfo();
+ globalThis.addEventListener("chatwoot:ready", handleChatwootReady);
+
+ // Check if Chatwoot is already ready
+ if (
+ (
+ globalThis as unknown as {
+ $chatwoot: {
+ setUser: (userId: string, userInfo: { email?: string | null; name?: string | null }) => void;
+ };
+ }
+ ).$chatwoot
+ ) {
+ setUserInfo();
+ }
+
+ return () => {
+ globalThis.removeEventListener("chatwoot:ready", handleChatwootReady);
+
+ const $chatwoot = (globalThis as unknown as { $chatwoot: { reset: () => void } }).$chatwoot;
+ if ($chatwoot) {
+ $chatwoot.reset();
+ }
+
+ const scriptElement = document.getElementById(CHATWOOT_SCRIPT_ID);
+ scriptElement?.remove();
+
+ userSetRef.current = false;
+ };
+ }, [chatwootBaseUrl, chatwootWebsiteToken, userId, userEmail, userName, setUserInfo]);
+
+ return null;
+};
diff --git a/apps/web/app/intercom/IntercomClient.tsx b/apps/web/app/intercom/IntercomClient.tsx
deleted file mode 100644
index 25581184ca..0000000000
--- a/apps/web/app/intercom/IntercomClient.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-"use client";
-
-import Intercom from "@intercom/messenger-js-sdk";
-import { useCallback, useEffect } from "react";
-import { TUser } from "@formbricks/types/user";
-
-interface IntercomClientProps {
- isIntercomConfigured: boolean;
- intercomUserHash?: string;
- user?: TUser | null;
- intercomAppId?: string;
-}
-
-export const IntercomClient = ({
- user,
- intercomUserHash,
- isIntercomConfigured,
- intercomAppId,
-}: IntercomClientProps) => {
- const initializeIntercom = useCallback(() => {
- let initParams = {};
-
- if (user && intercomUserHash) {
- const { id, name, email, createdAt } = user;
-
- initParams = {
- user_id: id,
- user_hash: intercomUserHash,
- name,
- email,
- created_at: createdAt ? Math.floor(createdAt.getTime() / 1000) : undefined,
- };
- }
-
- Intercom({
- app_id: intercomAppId!,
- ...initParams,
- });
- }, [user, intercomUserHash, intercomAppId]);
-
- useEffect(() => {
- try {
- if (isIntercomConfigured) {
- if (!intercomAppId) {
- throw new Error("Intercom app ID is required");
- }
-
- if (user && !intercomUserHash) {
- throw new Error("Intercom user hash is required");
- }
-
- initializeIntercom();
- }
-
- return () => {
- // Shutdown Intercom when component unmounts
- if (typeof window !== "undefined" && window.Intercom) {
- window.Intercom("shutdown");
- }
- };
- } catch (error) {
- console.error("Failed to initialize Intercom:", error);
- }
- }, [isIntercomConfigured, initializeIntercom, intercomAppId, intercomUserHash, user]);
-
- return null;
-};
diff --git a/apps/web/app/intercom/IntercomClientWrapper.tsx b/apps/web/app/intercom/IntercomClientWrapper.tsx
deleted file mode 100644
index 488e0fe898..0000000000
--- a/apps/web/app/intercom/IntercomClientWrapper.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { createHmac } from "crypto";
-import type { TUser } from "@formbricks/types/user";
-import { INTERCOM_APP_ID, INTERCOM_SECRET_KEY, IS_INTERCOM_CONFIGURED } from "@/lib/constants";
-import { IntercomClient } from "./IntercomClient";
-
-interface IntercomClientWrapperProps {
- user?: TUser | null;
-}
-
-export const IntercomClientWrapper = ({ user }: IntercomClientWrapperProps) => {
- let intercomUserHash: string | undefined;
- if (user) {
- const secretKey = INTERCOM_SECRET_KEY;
- if (secretKey) {
- intercomUserHash = createHmac("sha256", secretKey).update(user.id).digest("hex");
- }
- }
- return (
-
- );
-};
diff --git a/apps/web/lib/constants.ts b/apps/web/lib/constants.ts
index fb2afe4cda..3e6f93497a 100644
--- a/apps/web/lib/constants.ts
+++ b/apps/web/lib/constants.ts
@@ -215,9 +215,9 @@ export const BILLING_LIMITS = {
},
} as const;
-export const INTERCOM_SECRET_KEY = env.INTERCOM_SECRET_KEY;
-export const INTERCOM_APP_ID = env.INTERCOM_APP_ID;
-export const IS_INTERCOM_CONFIGURED = Boolean(env.INTERCOM_APP_ID && INTERCOM_SECRET_KEY);
+export const CHATWOOT_WEBSITE_TOKEN = env.CHATWOOT_WEBSITE_TOKEN;
+export const CHATWOOT_BASE_URL = env.CHATWOOT_BASE_URL || "https://app.chatwoot.com";
+export const IS_CHATWOOT_CONFIGURED = Boolean(env.CHATWOOT_WEBSITE_TOKEN);
export const TURNSTILE_SECRET_KEY = env.TURNSTILE_SECRET_KEY;
export const TURNSTILE_SITE_KEY = env.TURNSTILE_SITE_KEY;
diff --git a/apps/web/lib/env.ts b/apps/web/lib/env.ts
index 03226736ac..f75f5709f0 100644
--- a/apps/web/lib/env.ts
+++ b/apps/web/lib/env.ts
@@ -39,8 +39,8 @@ export const env = createEnv({
.or(z.string().refine((str) => str === "")),
IMPRINT_ADDRESS: z.string().optional(),
INVITE_DISABLED: z.enum(["1", "0"]).optional(),
- INTERCOM_SECRET_KEY: z.string().optional(),
- INTERCOM_APP_ID: z.string().optional(),
+ CHATWOOT_WEBSITE_TOKEN: z.string().optional(),
+ CHATWOOT_BASE_URL: z.string().url().optional(),
IS_FORMBRICKS_CLOUD: z.enum(["1", "0"]).optional(),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error", "fatal"]).optional(),
MAIL_FROM: z.string().email().optional(),
@@ -162,7 +162,8 @@ export const env = createEnv({
IMPRINT_URL: process.env.IMPRINT_URL,
IMPRINT_ADDRESS: process.env.IMPRINT_ADDRESS,
INVITE_DISABLED: process.env.INVITE_DISABLED,
- INTERCOM_SECRET_KEY: process.env.INTERCOM_SECRET_KEY,
+ CHATWOOT_WEBSITE_TOKEN: process.env.CHATWOOT_WEBSITE_TOKEN,
+ CHATWOOT_BASE_URL: process.env.CHATWOOT_BASE_URL,
IS_FORMBRICKS_CLOUD: process.env.IS_FORMBRICKS_CLOUD,
LOG_LEVEL: process.env.LOG_LEVEL,
MAIL_FROM: process.env.MAIL_FROM,
@@ -170,7 +171,6 @@ export const env = createEnv({
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
SENTRY_DSN: process.env.SENTRY_DSN,
OPENTELEMETRY_LISTENER_URL: process.env.OPENTELEMETRY_LISTENER_URL,
- INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
NOTION_OAUTH_CLIENT_ID: process.env.NOTION_OAUTH_CLIENT_ID,
NOTION_OAUTH_CLIENT_SECRET: process.env.NOTION_OAUTH_CLIENT_SECRET,
OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID,
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 01c090ab2e..274657c70a 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -61,10 +61,6 @@ const nextConfig = {
protocol: "https",
hostname: "images.unsplash.com",
},
- {
- protocol: "https",
- hostname: "api-iam.eu.intercom.io",
- },
],
},
async redirects() {
@@ -168,7 +164,7 @@ const nextConfig = {
},
{
key: "Content-Security-Policy",
- value: `default-src 'self'; script-src 'self' 'unsafe-inline'${scriptSrcUnsafeEval} https://*.intercom.io https://*.intercomcdn.com https:; style-src 'self' 'unsafe-inline' https://*.intercomcdn.com https:; img-src 'self' blob: data: http://localhost:9000 https://*.intercom.io https://*.intercomcdn.com https:; font-src 'self' data: https://*.intercomcdn.com https:; connect-src 'self' http://localhost:9000 https://*.intercom.io wss://*.intercom.io https://*.intercomcdn.com https:; frame-src 'self' https://*.intercom.io https://app.cal.com https:; media-src 'self' https:; object-src 'self' data: https:; base-uri 'self'; form-action 'self'`,
+ value: `default-src 'self'; script-src 'self' 'unsafe-inline'${scriptSrcUnsafeEval} https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' blob: data: http://localhost:9000 https:; font-src 'self' data: https:; connect-src 'self' http://localhost:9000 https: wss:; frame-src 'self' https://app.cal.com https:; media-src 'self' https:; object-src 'self' data: https:; base-uri 'self'; form-action 'self'`,
},
{
key: "Strict-Transport-Security",
diff --git a/apps/web/package.json b/apps/web/package.json
index eb31d6dc5c..20e05713b0 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -36,7 +36,6 @@
"@formbricks/surveys": "workspace:*",
"@formbricks/types": "workspace:*",
"@hookform/resolvers": "5.0.1",
- "@intercom/messenger-js-sdk": "0.0.14",
"@json2csv/node": "7.0.6",
"@lexical/code": "0.36.2",
"@lexical/link": "0.36.2",
diff --git a/packages/surveys/i18n.lock b/packages/surveys/i18n.lock
index c084e632ea..9fc90ac58e 100644
--- a/packages/surveys/i18n.lock
+++ b/packages/surveys/i18n.lock
@@ -29,6 +29,7 @@ checksums:
common/ranking_items: 463f2eb500f1b42fbce6cec17612fb9a
common/respondents_will_not_see_this_card: 18c3dd44d6ff6ca2310ad196b84f30d3
common/retry: 6e44d18639560596569a1278f9c83676
+ common/retrying: 0cb623dbdcbf16d3680f0180ceac734c
common/select_a_date: 521e4a705800da06d091fde3e801ce02
common/select_for_ranking: e5f4e20752d1c2d852cd02dc3a0e9dd0
common/sending_responses: 184772f70cca69424eaf34f73520789f
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 55ae671cb4..40d664c1b5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -162,9 +162,6 @@ importers:
'@hookform/resolvers':
specifier: 5.0.1
version: 5.0.1(react-hook-form@7.56.2(react@19.1.2))
- '@intercom/messenger-js-sdk':
- specifier: 0.0.14
- version: 0.0.14
'@json2csv/node':
specifier: 7.0.6
version: 7.0.6
@@ -2252,9 +2249,6 @@ packages:
cpu: [x64]
os: [win32]
- '@intercom/messenger-js-sdk@0.0.14':
- resolution: {integrity: sha512-2dH4BDAh9EI90K7hUkAdZ76W79LM45Sd1OBX7t6Vzy8twpNiQ5X+7sH9G5hlJlkSGnf+vFWlFcy9TOYAyEs1hA==}
-
'@isaacs/balanced-match@4.0.1':
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
engines: {node: 20 || >=22}
@@ -12235,8 +12229,6 @@ snapshots:
'@img/sharp-win32-x64@0.34.5':
optional: true
- '@intercom/messenger-js-sdk@0.0.14': {}
-
'@isaacs/balanced-match@4.0.1': {}
'@isaacs/brace-expansion@5.0.0':
diff --git a/turbo.json b/turbo.json
index 3178a6b024..ea4ce00866 100644
--- a/turbo.json
+++ b/turbo.json
@@ -146,8 +146,8 @@
"IMPRINT_ADDRESS",
"INVITE_DISABLED",
"IS_FORMBRICKS_CLOUD",
- "INTERCOM_APP_ID",
- "INTERCOM_SECRET_KEY",
+ "CHATWOOT_WEBSITE_TOKEN",
+ "CHATWOOT_BASE_URL",
"LOG_LEVEL",
"MAIL_FROM",
"MAIL_FROM_NAME",