Self-hosting Call Embed
Deploy the application as a Cloudflare Worker backed by PlanetScale Postgres and accounts with Ably and Daily. Each deployment serves a single organization.
Prerequisites
- A Cloudflare account with Workers and Hyperdrive access.
- A PlanetScale Postgres database for persistent application data.
- An Ably account for realtime chat and visit events.
- A Daily account if you enable video calls.
1. Clone the repository
Clone the repo and install the workspace dependencies.
git clone https://github.com/lukesteinbicker/embed.git
cd embed
pnpm install2. Configure your environment
Copy the Wrangler development secret template. At a minimum set BETTER_AUTH_SECRET, ABLY_API_KEY, DAILY_API_KEY, and DAILY_DOMAIN when video is enabled. For chat-only hosting, omit both Daily variables. Organization owners and admins can toggle calls in Settings → Organization. Claiming, chat, visit notes, and completion remain available in the dashboard.
cp apps/dashboard/.dev.vars.example apps/dashboard/.dev.vars
openssl rand -base64 32 # paste into BETTER_AUTH_SECRET3. Configure Hyperdrive and deploy
Create a Hyperdrive configuration from the PlanetScale direct URL, put its returned ID in apps/dashboard/wrangler.jsonc, run migrations, then deploy the Worker.
pnpm --filter @app/dashboard exec wrangler hyperdrive create embed-db --connection-string="YOUR_PLANETSCALE_DIRECT_URL"
DATABASE_DIRECT_URL="YOUR_PLANETSCALE_DIRECT_URL" pnpm db:migrate
pnpm deploy4. Create the first account
The first user to sign up becomes the administrator. After that, sign-ups are invite-only. Invite teammates from the dashboard, or set AUTH_STAFF_DOMAIN so anyone at your company domain can self-register.
5. Embed the widget
Create a website installation from the dashboard's Install page, then add its script to the customer site. The bundle is served from jsDelivr; point data-api at your deployment so the widget talks to your instance.
<script
src="https://cdn.jsdelivr.net/npm/@lukesteinbicker/callembed@0.1.14/dist/embed.iife.js"
data-token="YOUR_EMBED_TOKEN"
data-api="https://your-app.example.com"
defer
></script>Built-in rate limiting
The public widget endpoints (visit init, status updates, and realtime tokens) are unauthenticated, so a busy or hostile page can generate a lot of requests. The Worker's PUBLIC_RATE_LIMITER binding protects /api/contact/*. See Cloudflare's documentation.
Full documentation lives in the repository.