An AI-powered customer support platform that lets you embed a real-time chat widget on any website. Organizations manage conversations, configure AI behavior, and integrate voice support — all from a single dashboard.
Live Demo: https://echo-web-eight-umber.vercel.app
- Embeddable widget — drop a single
<script>tag on any site to add a floating chat button - AI chat — powered by Google Gemini with RAG for context-aware responses
- Voice calls — integrated Vapi AI voice assistant for phone-style support
- Real-time conversations — live inbox with unresolved / escalated / resolved status tracking
- Widget customization — configure greeting messages, quick-reply suggestions, primary color, and button position
- Domain allowlisting — restrict which domains can embed your widget
- Multi-tenant — organization-based access control via Clerk
- Contact sessions — capture visitor metadata (timezone, browser, screen size, referrer) automatically
- Plugin system — connect third-party services (Vapi credentials stored securely in AWS Secrets Manager)
- Billing — subscription management per organization
| Layer | Technology |
|---|---|
| Monorepo | Turborepo + pnpm |
| Dashboard | Next.js 15 (Turbopack) |
| Widget | Next.js 15 (Turbopack) |
| Embed script | Vite + TypeScript |
| Backend | Convex (real-time DB + serverless functions) |
| Auth | Clerk (organizations + webhooks) |
| AI | Google Gemini via Vercel AI SDK + @convex-dev/agent |
| RAG | @convex-dev/rag |
| Voice | Vapi AI |
| Secrets | AWS Secrets Manager |
| Styling | Tailwind CSS + shadcn/ui |
| Error tracking | Sentry |
echo/
├── apps/
│ ├── web/ # Dashboard (port 3000)
│ ├── widget/ # Embeddable chat UI (port 3001)
│ └── embed/ # Vanilla JS embed script (port 3002)
└── packages/
├── backend/ # Convex schema, functions, and AI agents
├── ui/ # Shared shadcn/ui component library
├── math/ # Shared utilities
├── eslint-config/
└── typescript-config/
- Node.js 20+
- pnpm 10+
- Convex account
- Clerk account
- Google AI Studio API key
- AWS account (for Secrets Manager)
- Vapi account (optional, for voice)
- Clone the repository:
git clone https://github.com/Rabinagurung/echo.git
cd echo- Install dependencies:
pnpm install-
Set up environment variables for each workspace (see Environment Variables).
-
Initialize the Convex backend:
cd packages/backend
pnpm setup- Run the full development stack from the root:
pnpm devThis starts all apps in parallel via Turborepo:
- Dashboard → http://localhost:3000
- Widget → http://localhost:3001
- Embed script → http://localhost:3002
NEXT_PUBLIC_CONVEX_URL=
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
NEXT_PUBLIC_CLERK_FRONTEND_API_URL=
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/conversations
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/conversations
SENTRY_AUTH_TOKEN=| Variable | Description | Where to get it |
|---|---|---|
NEXT_PUBLIC_CONVEX_URL |
Your Convex deployment URL | Convex dashboard → project settings |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY |
Clerk publishable key | Clerk dashboard → API Keys |
CLERK_SECRET_KEY |
Clerk secret key | Clerk dashboard → API Keys |
NEXT_PUBLIC_CLERK_FRONTEND_API_URL |
Clerk frontend API URL | Clerk dashboard → API Keys |
SENTRY_AUTH_TOKEN |
Sentry auth token for source maps | Sentry → Settings → Auth Tokens |
NEXT_PUBLIC_CONVEX_URL=| Variable | Description | Where to get it |
|---|---|---|
NEXT_PUBLIC_CONVEX_URL |
Same Convex deployment URL as the dashboard | Convex dashboard |
CONVEX_DEPLOYMENT=
CONVEX_URL=
CLERK_JWT_ISSUER_DOMAIN=
CLERK_SECRET_KEY=
CLERK_WEBHOOK_SECRET=
GOOGLE_GENERATIVE_AI_API_KEY=
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=| Variable | Description | Where to get it |
|---|---|---|
CONVEX_DEPLOYMENT |
Convex deployment identifier (e.g. dev:my-project-123) |
Generated by convex dev |
CONVEX_URL |
Convex deployment URL | Convex dashboard |
CLERK_JWT_ISSUER_DOMAIN |
Clerk JWT issuer domain | Clerk dashboard → JWT Templates |
CLERK_SECRET_KEY |
Clerk secret key | Clerk dashboard → API Keys |
CLERK_WEBHOOK_SECRET |
Clerk webhook signing secret | Clerk dashboard → Webhooks → your endpoint |
GOOGLE_GENERATIVE_AI_API_KEY |
Google Gemini API key | Google AI Studio |
AWS_REGION |
AWS region for Secrets Manager | AWS console |
AWS_ACCESS_KEY_ID |
AWS IAM access key | AWS IAM → Security credentials |
AWS_SECRET_ACCESS_KEY |
AWS IAM secret key | AWS IAM → Security credentials |
CLERK_GUEST_USER_ID |
Clerk user ID of the pre-provisioned demo account used by "Continue as Guest" | See Guest Sign-In below |
VITE_WIDGET_URL=http://localhost:3001| Variable | Description |
|---|---|
VITE_WIDGET_URL |
URL of the deployed widget app, baked into the embed script at build time |
The dashboard sign-in page has a "Continue as Guest" button so people evaluating the project (e.g. recruiters) can explore it without creating an account. It signs the visitor into a single pre-provisioned demo account whose write access is disabled at the API layer, so it's safe to share — guests can view everything but can't send messages, change settings, disconnect plugins, upload/delete files, or overwrite secrets.
One-time setup in the Clerk Dashboard:
- Users → Create user — make a dedicated demo account (e.g.
guest-demo@yourdomain.com), no password required. - Sign in as that user once and create/join an Organization for it — this becomes the shared demo workspace. Populate it from the dashboard with a few example conversations, widget settings, etc. so guests see a populated demo instead of an empty one.
- Organizations → Roles — create a custom role with the key
org:guest(name/permissions don't matter; the app enforces read-only access itself, not Clerk's built-in permission checks). - Open that organization → Members, and set the demo user's role to
org:guest. - Copy the demo user's ID from Users → (the demo user) — it's shown at the top and starts with
user_. - Set it as a Convex environment variable:
cd packages/backend
npx convex env set CLERK_GUEST_USER_ID user_xxxxxxxxxxxxNotes:
- If your Clerk plan doesn't support custom organization roles, swap the
org:guestrole check inpackages/backend/convex/private/checkUserIdentityAndGetOrgId.tsfor a check on the user'spublicMetadata(e.g.{ isGuest: true }) exposed via a custom claim in your Clerk JWT template instead. - The write guard runs server-side on every mutation, so even a guest calling a mutation directly from devtools gets rejected with a
GUEST_READ_ONLYerror — the button is just the convenient path in. - Until
CLERK_GUEST_USER_IDis set, clicking "Continue as Guest" fails with "Guest sign-in is unavailable right now."
Once deployed, add the following snippet to any website:
<script
src="https://your-domain.com/embed.js"
data-organization-id="YOUR_ORG_ID"
data-position="bottom-right"
data-primary-color="#3b82f6"
async
></script>| Attribute | Required | Default | Description |
|---|---|---|---|
data-organization-id |
Yes | — | Your Clerk organization ID |
data-position |
No | bottom-right |
bottom-right or bottom-left |
data-primary-color |
No | #3b82f6 |
Any valid CSS color value |
- Push the repo to GitHub.
- Import each app (
apps/web,apps/widget) as separate Vercel projects. - Set the root directory for each project accordingly.
- Add the environment variables under Project Settings → Environment Variables.
- Build and deploy.
cd packages/backend
npx convex deploycd apps/embed
pnpm buildUpload dist/embed.js to a CDN or your static hosting provider.