Skip to content
DeltaCrab
Sign inStart free trial

Integration guide

Everything an engineer or coding agent needs to install DeltaCrab.

Documentation version: widget v1. Do not mix with other versions.

Overview

The DeltaCrab widget is a script that creates an iframe on your site. The iframe runs on the DeltaCrab domain; your site never sees tokens or messages.

The widget only loads on origins allowed on the Install page. Start in visitor mode; add verified identity when your app has a server.

  • 1. Copy the public site key from the Install page.
  • 2. Add your app's origin (for example https://app.example.com) to Allowed origins.
  • 3. Paste the snippet into your global layout.
  • 4. Open your site, send a message, and answer it from the inbox.

HTML / any site

Paste before </body> on every page, or once in your main layout.

HTML
<script>
  window.DeltaCrabSettings = { siteKey: "dc_pk_YOUR_SITE_KEY" };
  window.DeltaCrab = window.DeltaCrab || function () { (window.DeltaCrab.q = window.DeltaCrab.q || []).push(arguments); };
</script>
<script async src="https://app.deltacrab.com/widget.js"></script>

Next.js (App Router)

Add once in app/layout.tsx. Works with Server Components; the script loads after hydration.

app/layout.tsx
// app/layout.tsx — add once, inside <body>
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script id="deltacrab-settings" strategy="afterInteractive">
          {`window.DeltaCrabSettings = { siteKey: "dc_pk_YOUR_SITE_KEY" };
window.DeltaCrab = window.DeltaCrab || function () { (window.DeltaCrab.q = window.DeltaCrab.q || []).push(arguments); };`}
        </Script>
        <Script src="https://app.deltacrab.com/widget.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}

// Optional, from any client component:
//   window.DeltaCrab?.("setLocale", "pt-BR");
//   window.DeltaCrab?.("setTheme", "dark");
//   window.DeltaCrab?.("open");

Verified identity

Your server signs a short-lived HS256 token with the secret generated in Install → Verified identity. The secret stays on the server, in the DELTACRAB_IDENTITY_SECRET environment variable.

Required claims: site_key, sub (stable customer ID from your database), iat, exp (at most 10 minutes after iat) and jti (unique; each token works once). Accepted attributes: name, email, plan, organization, account_id, locale. Other fields are ignored.

Request a fresh token and call identify on every page load while the user is signed in, and right after login or an account switch. Call logout on sign-out. Anonymous history is never merged into the identified customer.

When you rotate the secret, the previous one keeps working for 24 hours so you can deploy the new one without signing anyone out.

app/api/deltacrab-token/route.ts
// app/api/deltacrab-token/route.ts — runs on YOUR server only.
// Requires DELTACRAB_IDENTITY_SECRET (from DeltaCrab → Install → Verified identity).
import { createHmac, randomUUID } from "node:crypto";
import { NextResponse } from "next/server";
import { getSession } from "@/lib/auth"; // your existing server-side session helper

function base64url(input: Buffer | string) {
  return Buffer.from(input).toString("base64url");
}

export async function POST() {
  const session = await getSession();
  if (!session?.user) return NextResponse.json({ error: "unauthenticated" }, { status: 401 });

  const now = Math.floor(Date.now() / 1000);
  const header = base64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
  const payload = base64url(JSON.stringify({
    site_key: "dc_pk_YOUR_SITE_KEY",
    sub: String(session.user.id),          // stable customer ID from YOUR database
    iat: now,
    exp: now + 300,                         // at most 10 minutes
    jti: randomUUID(),                      // single use
    name: session.user.name,                // optional, allowlisted attributes:
    email: session.user.email,              // name, email, plan, organization, account_id, locale
  }));
  const signature = createHmac("sha256", process.env.DELTACRAB_IDENTITY_SECRET!)
    .update(`${header}.${payload}`)
    .digest("base64url");
  return NextResponse.json({ token: `${header}.${payload}.${signature}` });
}

// In the browser, on every page load while the user is signed in (tokens are
// single use and short-lived, so fetch a fresh one each time), and right
// after login or an account switch:
//   const { token } = await fetch("/api/deltacrab-token", { method: "POST" }).then(r => r.json());
//   window.DeltaCrab("identify", { token });
// On logout:
//   window.DeltaCrab("logout");
// Widget script: https://app.deltacrab.com/widget.js

Widget API

Commands can be called before the script finishes loading: the snippet's stub queues them and the loader runs them in order.

JavaScript
window.DeltaCrab("open");                    // open the panel
window.DeltaCrab("close");                   // close the panel
window.DeltaCrab("setLocale", "pt-BR");      // en | pt-BR | es
window.DeltaCrab("setTheme", "dark");        // light | dark | system
window.DeltaCrab("identify", { token });     // signed identity token from YOUR server
window.DeltaCrab("logout");                  // forget the signed-in customer on this device

Content Security Policy

If your app sets a CSP, allow the DeltaCrab origin in these directives:

CSP
Content-Security-Policy:
  script-src  'self' https://app.deltacrab.com;
  frame-src   https://app.deltacrab.com;
  connect-src 'self' https://app.deltacrab.com;

Troubleshooting

The browser console and the Diagnostics section of the Install page show the cause.

  • "not in the allowed origins": add the exact origin (scheme + host + port) to Allowed origins.
  • "Unknown site key": copy the snippet again from the Install page.
  • frame-ancestors refusal in the console: the origin is not allowed for this site.
  • Identity rejected: expired (token too old), replay (jti reused), bad_signature (wrong secret), wrong_site (site_key mismatch), lifetime_too_long (exp - iat above the limit).
  • Nothing shows up: check your CSP (script-src, frame-src, connect-src).