How to add Crisp live chat to a Webflow Cloud app the right way

Learn how to add Crisp live chat to a Webflow Cloud app, identify signed-in users, and verify their identity from an edge Route Handler with Web Crypto.

How to add Crisp live chat to a Webflow Cloud app the right way

Ismail Ajagbe
Technical Author
View author profile
Ismail Ajagbe
Technical Author
View author profile
Table of contents

Crisp's chatbox is a browser-only script, so the real work on Webflow Cloud is personalizing it with a verified user identity you sign on the server.

Webflow Cloud runs your app as a Cloudflare Worker, and that one fact decides how you add Crisp. The chatbox itself is a client-side script that runs only in the browser, so it never directly touches the edge runtime.

The parts that do touch the edge are the pieces that make the chat useful: knowing who the signed-in user is, and proving that identity to Crisp so your support team can trust it.

The setup splits into two halves. The first half is the widget: install the crisp-sdk-web package, mount it in a Client Component, and load it from your root layout. The second half is personalization: pass the logged-in user's email, plan, and events to Crisp, then sign that email server-side so it shows up verified in your inbox instead of as an anonymous visitor.

In this guide, we build both halves in six steps, starting with a working chatbox and ending with a verified, personalized widget you drive from your own code.

What do you need to add Crisp live chat to a Webflow Cloud app?

You need a Webflow Cloud Next.js app and a Crisp account. Crisp's free plan covers the widget and basic user data, and cryptographic identity verification requires a paid Crisp plan (Mini or higher).

Here's the full list of the requirements:

  • A Webflow Cloud project running a Next.js app, deployed or in local dev
  • A Crisp account with a workspace (app.crisp.chat)
  • Your Crisp Website ID (from Workspace Settings)
  • A Crisp Mini plan or higher, only if you want a verified identity (the green checkmark)
  • Node.js 22.13.0 or higher locally, the floor set by Webflow CLI 2.x

The widget itself takes about five minutes: one package and one Client Component. The identity signing adds a single Route Handler. If you already have authentication wired up, for example, through Auth0 authentication on a Webflow site, the email and user fields you pass to Crisp are the same ones you already have on the server.

6 steps to add Crisp live chat to a Webflow Cloud app

The pattern moves from the simplest possible widget to a fully personalized one. Steps one through three put a working chatbox on the page. Steps four through six identify the visitor, sign that identity on the edge, and hand control of the widget to your own code.

Here is the sequence I follow on every Webflow Cloud project that ships a chat widget.

1. Create a Crisp workspace and copy your Website ID

Every Crisp integration is keyed to a Website ID, a short string that tells the chatbox which workspace to load. Create it first, because nothing else works without it.

Log in to app.crisp.chat and create a workspace if you do not have one. Then go to Settings > Workspace Settings > Setup & Integrations and find the Website ID field. Copy the value.

The Website ID is not a secret. It ships in the browser on every page that loads the chatbox, so that anyone can read it in your client bundle, and that is by design. Treat it like a public identifier rather than an API key.

That distinction matters later, because it changes how you store each value: the public Website ID is safe to expose to the browser through a NEXT_PUBLIC_ environment variable. At the same time, the secret signing key from step five must stay server-side. Keep the Website ID handy for the next step.

2. Install the Crisp Web SDK and create a client component

The chatbox must load only in the browser. In a Next.js app on Webflow Cloud, that means a Client Component, because Server Components and the edge runtime have no window object for Crisp to attach to.

From the root of your Webflow Cloud project, install the official package:

npm install crisp-sdk-web

The crisp-sdk-web package wraps Crisp's underlying $crisp JavaScript SDK and adds TypeScript definitions, so you get autocompletion instead of raw string commands. It is the setup Crisp documents for React, Vue, and Angular apps.

Then create app/components/CrispChat.tsx as a Client Component:

// app/components/CrispChat.tsx
"use client";

import { useEffect } from "react";
import { Crisp } from "crisp-sdk-web";

export default function CrispChat() {
  useEffect(() => {
    // The NEXT_PUBLIC_ prefix exposes the public Website ID to the browser.
    Crisp.configure(process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID!);
  }, []);

  return null;
}

The "use client" directive is the load-bearing line here. It tells Next.js to render this component in the browser, where Crisp.configure() can run. The useEffect hook delays the call until after the component mounts, so it never executes during server rendering.

The component returns null because Crisp injects its own chatbox markup, leaving nothing for React to render. Set NEXT_PUBLIC_CRISP_WEBSITE_ID in your Webflow Cloud environment variables, where the NEXT_PUBLIC_ prefix is what makes the value readable from the browser.

3. Load Crisp from your Webflow Cloud root layout

A chatbox should follow the user across every page, so it belongs in the layout rather than in any single route. Loading it once at the root means it persists through client-side navigation instead of reloading on each page.

Import the component in app/layout.tsx:

// app/layout.tsx
import CrispChat from "./components/CrispChat";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <CrispChat />
        {children}
      </body>
    </html>
  );
}

Placing <CrispChat /> inside <body> and above {children} keeps the widget mounted for the entire session. Because it is a Client Component sitting inside a Server Component layout, Next.js renders the page shell on the edge and hydrates only the chatbox in the browser.

Deploy this, open your app at its mount path, and the Crisp bubble appears in the corner. At this point, you have a working widget that talks to anonymous visitors.

4. Identify signed-in users and push session data and events

An anonymous chat is a missed opportunity. The moment a user logs in, you can hand Crisp their email, name, plan, and behavior, so your team sees context instead of a blank profile. This is where the chatbox starts earning its place.

Pass the authenticated user into the component and set the fields inside the same effect:

// app/components/CrispChat.tsx
"use client";

import { useEffect } from "react";
import { Crisp } from "crisp-sdk-web";

type Props = {
  user?: { id: string; email: string; name: string; plan: string };
};

export default function CrispChat({ user }: Props) {
  useEffect(() => {
    Crisp.configure(process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID!);

    if (user) {
      Crisp.user.setEmail(user.email);
      Crisp.user.setNickname(user.name);
      Crisp.session.setData({ user_id: user.id, plan: user.plan });
      Crisp.session.pushEvent("app_opened");
    }
  }, [user]);

  return null;
}

The Crisp.user methods populate the contact card your operators see. The Crisp.session.setData() call attaches custom fields you can filter and search on, such as the user's plan, and Crisp.session.pushEvent() records a timeline event you can use to trigger automated campaigns or bot scenarios.

Pass the user prop down from a parent that already has the session, and every conversation arrives pre-filled. The one thing this does not do is prove the email is real, which is the next step.

5. Sign the user's email server-side with Web Crypto

Anyone can call Crisp.user.setEmail() with any address, so an unsigned email is just a claim. Identity verification fixes that: you sign the email with a secret key only your server and Crisp know, and Crisp shows a verified badge when the signature checks out.

The signing has to happen on the server, and on Webflow Cloud's edge runtime, which means Web Crypto.

First, get your secret key.

In Crisp, go to Settings > Workspace Settings > Advanced configuration, scroll to Identity Verification, and enable "Verify user emails with cryptographic signatures." Copy the generated key.

Store it in Webflow Cloud as an environment variable: open your environment's Deployments Dashboard, click Environment Variables, choose Add variable > Add single variable, name it CRISP_SECRET_KEY, paste the value, and mark it as a Secret so it stays encrypted and masked.

Then create a Route Handler that signs the email:

// app/api/crisp/sign/route.ts
import { NextResponse, type NextRequest } from "next/server";

export async function POST(request: NextRequest) {
  const { email } = (await request.json()) as { email: string };

  // Read the secret at runtime from Webflow Cloud's environment.
  const secret = process.env.CRISP_SECRET_KEY as string;
  // Import the key and sign with HMAC-SHA256 using the Web Crypto API.
  const key = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );

  const signed = await crypto.subtle.sign(
    "HMAC",
    key,
    new TextEncoder().encode(email)
  );

  // Crisp expects the signature as a hex string.
  const signature = Array.from(new Uint8Array(signed))
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");

  return NextResponse.json({ signature });
}

This handler reads the secret inside the function body rather than at the top level of the module, so the value resolves inside the request context. Webflow Cloud makes Secrets and environment variables available to the build and to the deployed app at runtime.

Read the value inside the handler rather than at module top level, which is the pattern Webflow documents for reaching runtime configuration. Storage bindings such as KV or SQLite are reached separately through getCloudflareContext(). It signs the email with crypto.subtle, the Web Crypto implementation that Webflow Cloud lists as the edge-native replacement for Node's crypto.

The output is converted to the hex digest Crisp expects. Crisp requires HMAC-SHA256 specifically and rejects any other digest, which the hash: "SHA-256" option guarantees.

6. Set the verified email on the chatbox and control the widget

With the signature coming from your edge handler, the last step closes the loop: fetch the signature, set the email with it, and the conversation shows as verified. From there, you can drive the widget from your own UI.

In the client component, request the signature and pass it as the second argument to setEmail:

// Inside the useEffect, when a user is present:
(async () => {
    const res = await fetch(`${baseUrl}/api/crisp/sign`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email: user.email }),
  });

  const { signature } = await res.json();

  // The second argument is the HMAC signature from your Route Handler.
  Crisp.user.setEmail(user.email, signature);

  // Open the chatbox from your own button, for example.
  Crisp.chat.open();
})();

The baseUrl prefix matters on Webflow Cloud because your app is mounted at a base path such as /app, so a bare /api/crisp/sign would resolve against the wrong root. Build it from config.basePath in your next.config.js instead of hard-coding it.

Passing the signature to setEmail is what turns the green checkmark on in your Crisp inbox. Methods like Crisp.chat.open(), Crisp.chat.show(), and Crisp.chat.hide() let you wire the widget to your own buttons, so a "Talk to us" link in your nav opens the chat instead of relying only on the default bubble.

What breaks Crisp live chat in Webflow Cloud?

Most Crisp problems on Webflow Cloud trace back to one of four things:

  • The widget loading on the server instead of the browser
  • The Website ID being read from the wrong place
  • A signature that does not match
  • A Route Handler reaching for a Node.js API that the edge runtime does not want you to use

Each one fails in a way that does not point straight at the cause.

Here is how to recognize each one and what to change.

The chatbox never appears, or the build fails with "window is not defined"

Crisp is being imported or configured outside the browser. If Crisp.configure() runs in a Server Component, at a module's top level, or in any code path that executes during server rendering, there is no window for the SDK to attach to, and the build or the request throws.

Keep all Crisp calls inside a Client Component marked with "use client", and call Crisp.configure() inside a useEffect so it only runs after the component mounts in the browser. The component should return null. This is the single most common mistake when adding any browser-only script to a Next.js app on the edge.

The Website ID reads as undefined in the browser

The variable is missing the NEXT_PUBLIC_ prefix, or it was never set for the deployed environment. Next.js only exposes variables that start with NEXT_PUBLIC_ to client-side code. Hence, a variable named CRISP_WEBSITE_ID stays server-only and reads as undefined inside your Client Component, leaving Crisp.configure() with nothing to load.

Rename the variable to NEXT_PUBLIC_CRISP_WEBSITE_ID and confirm it is set in your environment's Environment Variables dashboard, not only in a local .env file. Because the Website ID is public, it does not need to be marked as a Secret.

Redeploy after changing it, since a new deployment is what picks up updated environment variables.

Every conversation shows as Unverified in the Crisp inbox

The signature does not match. Crisp is strict about the algorithm and the input, so a verified email becomes Unverified if you sign with the wrong digest, sign a different string than the one you set for the email, or use a key that does not match the one in your workspace.

Confirm you sign the exact email string with HMAC-SHA256 and return a hex digest, then pass that digest as the second argument to Crisp.user.setEmail(). Check that CRISP_SECRET_KEY matches the key under Advanced configuration. Verification also requires a paid Crisp plan, so confirm the workspace is on Mini or higher.

The signing Route Handler throws or returns 500 after deploy

The handler is using a Node.js crypto pattern that the Workers runtime does not support cleanly. Crisp's own example uses crypto.createHmac from Node, and while Webflow Cloud enables Node.js compatibility, the docs explicitly steer hashing and signing toward crypto.subtle instead.

Switch the handler to the Web Crypto API as shown in step five: crypto.subtle.importKey followed by crypto.subtle.sign.

Read the secret from process.env inside the handler rather than at the top of the module, so it resolves inside the request context.

If you want to catch handler errors like these in production, Sentry error tracking on a Webflow Cloud app surfaces the stack trace.

Extend Crisp live chat across your Webflow Cloud app

The setup in this guide gives you a verified, identified chatbox driven from the edge. From there, the natural extensions move the logic from the browser into your Route Handlers and out to Crisp's backend APIs.

The first extension on most projects is server-to-server messaging. Instead of only reacting to visitors, your Webflow Cloud app can initiate conversations, send messages, and create contacts via Crisp's REST API when something occurs on the server, such as an order, an invoice, or a trial ending.

That backend work pairs naturally with the data layer behind a real-time dashboard using Supabase on Webflow Cloud, and it follows the same pattern as a larger full-stack app on Webflow Cloud.

If you want to go further into automated, AI-driven replies, an AI-powered chat interface on a Webflow site with Claude demonstrates the server-side proxy pattern, which keeps your model keys off the client.

Explore Webflow + Crisp for the full breakdown of installation options, from a Code Embed on a static Webflow page to API-driven workflows that sync form submissions into Crisp contacts.

Frequently asked questions

Does the Crisp widget run on Webflow Cloud's edge runtime?

The widget runs in the browser, not on the edge, so the runtime never loads it directly. Only your signing Route Handler runs on the edge, and it uses the Web Crypto API. The chatbox loads asynchronously from Crisp's own CDN.

Is the Crisp Website ID a secret I should hide?

No. The Website ID ships in the browser on every page that loads the chatbox, so it is a public identifier by design. Expose it with a NEXT_PUBLIC_ environment variable. Only the identity verification key is secret and must remain server-side.

Do I need a paid Crisp plan to add live chat?

No, for the widget itself. Crisp's free plan includes the chatbox, two seats, and unlimited conversations, but caps you at 100 customer profiles, which starts to matter once you identify every signed-in user. Cryptographic identity verification, the green checkmark in the inbox, is the part that requires a paid plan, starting at Crisp Mini.

Can I add Crisp to a regular Webflow site instead of a Webflow Cloud app?

Yes. On a standard Webflow site, paste the Crisp script into Site Settings > Custom Code or a page Embed element. Webflow Cloud is what you need when the chat needs to respond to server-side logic, such as signed-in users and verified identity.

Does Crisp use cookies that I need to disclose?

Yes. Crisp relies on cookies to persist sessions across visits, so it falls under your consent banner. If you manage consent with a tool like Cookiebot GDPR cookie consent on a Webflow site, include Crisp in that flow.

How do I open the chatbox from my own button?

Call Crisp.chat.open() from a click handler in any Client Component. If you defer loading with Crisp.configure(id, { autoload: false }), calling open() or show() implicitly calls Crisp.load() for you. It is the same trigger pattern behind a Calendly pop-up modal in Webflow.


Last Updated
August 8, 2026
Category

Related articles

How to prevent page scroll when a modal is open in Webflow + the iOS Safari fix
How to prevent page scroll when a modal is open in Webflow + the iOS Safari fix

How to prevent page scroll when a modal is open in Webflow + the iOS Safari fix

How to prevent page scroll when a modal is open in Webflow + the iOS Safari fix

Development
By
Colin Lateano
,
,
Read article
Why does your Airtable-to-Webflow Zap keep failing, and how do you fix it
Why does your Airtable-to-Webflow Zap keep failing, and how do you fix it

Why does your Airtable-to-Webflow Zap keep failing, and how do you fix it

Why does your Airtable-to-Webflow Zap keep failing, and how do you fix it

Development
By
Colin Lateano
,
,
Read article
How to add Gemini AI to a Webflow site securely using Webflow Cloud
How to add Gemini AI to a Webflow site securely using Webflow Cloud

How to add Gemini AI to a Webflow site securely using Webflow Cloud

How to add Gemini AI to a Webflow site securely using Webflow Cloud

Development
By
Colin Lateano
,
,
Read article
How to use the Webflow custom code API to push scripts to specific pages
How to use the Webflow custom code API to push scripts to specific pages

How to use the Webflow custom code API to push scripts to specific pages

How to use the Webflow custom code API to push scripts to specific pages

Development
By
Colin Lateano
,
,
Read article

verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logosoundcloud logoreddit logothe new york times logoideo logoupwork logodiscord logo
verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logosoundcloud logoreddit logothe new york times logoideo logoupwork logodiscord logo

Get started for free

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.

Get started — it’s free
Watch demo

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.