> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Customer service

> Drop a chat widget on every page — pick from Crisp, Tawk.to, Intercom, or Chatwoot with a single config flip.

Live chat is a credibility signal — a product without it looks like a hobby project,
a product with it looks like there's a team behind it. But every chat tool ships its
own SDK, auth model, and unmount quirks; rolling your own integration burns days when
you should be shipping features.

vibestrap mounts a `<CustomerService />` widget in the locale layout that switches
between four major providers (Crisp, Tawk.to, Intercom, Chatwoot) with a single
config flip and a public env var. Providers self-gate on their env vars, so an
unconfigured one renders nothing instead of crashing the page.

## Prerequisites

* An account with one of: Crisp, Tawk.to, Intercom, or Chatwoot.
* The site/property/app ID from that provider's dashboard.
* Your production domain allow-listed in the provider's settings (most reject
  embedded widgets from unknown origins).

## Step-by-step

1. **Pick a provider** in `src/config/site.ts`:

   ```ts theme={null}
   customerService: {
     enable: true,
     provider: 'crisp', // 'crisp' | 'tawk' | 'intercom' | 'chatwoot'
   },
   ```

2. **Add the matching env vars** to `.env.local`. All are `NEXT_PUBLIC_*` —
   they need to ship to the browser so the loader script can boot.

   ```bash theme={null}
   # Crisp
   NEXT_PUBLIC_CRISP_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

   # Tawk.to (both required)
   NEXT_PUBLIC_TAWK_PROPERTY_ID=000000000000000000000000
   NEXT_PUBLIC_TAWK_WIDGET_ID=000000000

   # Intercom
   NEXT_PUBLIC_INTERCOM_APP_ID=abcd1234

   # Chatwoot (token required, base URL defaults to app.chatwoot.com)
   NEXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx
   NEXT_PUBLIC_CHATWOOT_BASE_URL=https://app.chatwoot.com
   ```

3. **Allow-list your domain** in the provider's dashboard (Crisp → Settings →
   Website Settings, Tawk → Property → Widget → Restrictions, etc).

4. **Restart `pnpm dev`** — public env vars are baked at build time.

## Pick the right provider

| Provider | When to pick                                                                                                              |
| -------- | ------------------------------------------------------------------------------------------------------------------------- |
| Crisp    | Generous free tier (2 seats, unlimited contacts), nice dashboard, fastest setup. Good default for indie hackers.          |
| Tawk.to  | Fully free forever — no plan limits at all. Trade-off: branded widget unless you pay \$19/mo to remove it.                |
| Intercom | Enterprise standard. Pick this if you have a real support team and want product tours, ticketing, and outbound campaigns. |
| Chatwoot | Open-source, self-hostable. Pick this if you need data sovereignty or want to host the chat backend on your own infra.    |

## Verify it works

1. Open the site in an incognito window — the bubble should appear bottom-right
   within \~1 second of page load.
2. Send a test message; check the inbox in the provider's dashboard.
3. Visit `/login` or `/register` — the widget is intentionally hidden on auth
   pages (the `<CustomerService />` component lives in the app layout, not the
   auth layout).
4. View source: you should see exactly one loader script tag for your provider
   and zero for the others.

## Common pitfalls

1. **Missing `NEXT_PUBLIC_` prefix.** Anything the browser reads needs the
   prefix; otherwise Next.js strips it from the bundle and the widget silently
   fails to mount.
2. **Forgot to allow-list your domain.** Most providers reject the widget on
   unknown origins (you'll see an empty bubble or a CORS error in devtools).
3. **CSP blocking the script.** If you've added a Content-Security-Policy
   header, add the provider's CDN to `script-src` and `connect-src` (e.g.
   `https://client.crisp.chat`, `https://embed.tawk.to`,
   `https://widget.intercom.io`, your Chatwoot base URL).
4. **Two widgets at once.** Setting `enable: true` plus a stale env var from a
   previous provider doesn't double-render — the switch in `src/customer-service/index.tsx`
   only mounts the active one. But leftover env vars are harmless; clean them up.
5. **Widget on auth pages.** It's hidden by design (separate layout). If you
   want it everywhere, mount `<CustomerService />` in the root layout instead
   of the app layout.

## Add a new provider

The pattern is intentionally tiny — a single client component that reads its
env var and returns a `<Script>` tag (or `null` when unset). Look at any of
`src/customer-service/provider/*.tsx` for the shape, then:

1. Add the provider to the union in `src/customer-service/types.ts`.
2. Add a new `case` to the switch in `src/customer-service/index.tsx`.
3. Add the env var to `src/env.ts` under `client.NEXT_PUBLIC_*`.

## Official docs

* Crisp: [docs.crisp.chat](https://docs.crisp.chat/)
* Tawk.to: [help.tawk.to](https://help.tawk.to/)
* Intercom: [intercom.com/help/en](https://www.intercom.com/help/en/)
* Chatwoot: [chatwoot.com/docs](https://www.chatwoot.com/docs)
