Custard AISign in

Developer guide

Put your Custard AI staff inside your own website or product. Visitors talk or type instead of filling in forms, and your system gets the result.

Step 1

Add it to your site

Paste this before the closing body tag. Find your staff ID in AI Staff, On your website, where you also list the web addresses allowed to show it.

<script src="https://custardai.dev/widget.js"
  data-agent="YOUR_STAFF_ID" async></script>

Works on any site: WordPress, Shopify, Webflow, Squarespace, React, Next.js. Shape and colour you pick in the dashboard update by themselves.

Step 2

Tell it who the visitor is

If the visitor is signed in, pass their details. Your AI staff knows them at once, and the same values come back to you after the conversation.

<script>
  // Call any time, before or after the chat opens.
  window.CustardWidget?.setContext({
    userId: "u_123",
    email: "ana@example.com",
    fullName: "Ana Silva",
    plan: "pro"
  });
</script>

Flat values only (text, numbers, true/false), up to 20 keys, text up to 500 characters.

Step 3

Let it show people around

List the pages your AI staff may open and the things it may point at. It only ever picks one of your labels, so it can never send a visitor somewhere you did not allow.

window.CustardWidget?.setGuide({
  pages: [
    { label: "Pricing", url: "/pricing" },
    { label: "Create account", url: "/signup" }
  ],
  spots: [
    { label: "Sign up button", selector: "#signup" },
    { label: "Plan picker", selector: "[data-plans]" }
  ]
});

Opening a page loads it on the same site. Pointing scrolls to the element and draws a soft ring around it for a few seconds. Links to other websites are ignored.

When a new page loads, the chat opens again by itself with the conversation so far; a voice call pauses until the visitor taps the mic. On single page apps, handle the move yourself so a voice call never pauses:

// Single page apps: move with your own router so the voice call keeps going.
window.CustardWidget?.onAction((a) => {
  if (a.action === "open_page") {
    router.navigate(a.url);
    return true; // handled, Custard does nothing else
  }
});

// Or listen for the event anywhere on the page:
window.addEventListener("custard:action", (e) => {
  console.log(e.detail); // { action, label, url, selector }
});

Step 4

Get the result after each conversation

In Rules & connections, paste your link into Send call summaries and tap Connect. When a website conversation ends, Custard AI posts this to your link:

{
  "event": "conversation.finished",
  "channel": "widget_voice",
  "conversation_id": "7c1f…",
  "agent_id": "a2b9…",
  "started_at": "2026-10-30T17:40:02Z",
  "ended_at": "2026-10-30T17:46:31Z",
  "duration_seconds": 389,
  "status": "completed",
  "metadata": { "userId": "u_123", "email": "ana@example.com" },
  "customer": { "name": "Ana Silva", "phone": null, "email": "ana@example.com" },
  "summary": "Ana asked about the Pro plan and booked a demo.",
  "extracted_data": { "company": "Silva Studio", "team_size": "12" },
  "intent": "demo",
  "needs_follow_up": false,
  "transcript": [{ "role": "user", "text": "Hi…" }],
  "recording_url": null
}

The Connect button sends the same shape with "test": true, so answer 200 to it. Calls are not recorded, so recording_url is always null.

Get answers as fields, ready for your database

Add one line to the staff member's instructions, for example Extract fields: profession, interests, evening_style. You can add a line of guidance under it. Each delivery then carries "extracted_data": { "profession": "Chef", "interests": ["Wine", "Art"], "evening_style": null }, using only what the person said (null when they didn't say it). It is null when no fields are set. Use it to pre-fill a profile or create the account.

Change how staff behave per visitor

Everything you pass to setContext is shown to the staff member as facts, never commands. Write rules in the instructions such as "If memberStatus is approved, act as their concierge; otherwise interview them".

Check it really came from us

Tap Show secret under Send call summaries. Every delivery has an X-Custard-Signature header: an HMAC SHA256 of the timestamp, a dot and the raw body. Reject anything that does not match or is older than 5 minutes.

import crypto from "node:crypto";

export function verifyCustard(rawBody, header, secret) {
  // header looks like: t=1730310391,v1=5f2c…
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(parts.t + "." + rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Step 5

Let it check and change things live

In Rules & connections, add your link under live lookup and an optional secret. Turn on Live lookups (and Live changes, if you want) for the staff member. During a conversation your AI staff can then check an order or account, and after the customer clearly says yes, ask your system to make a change.

// Custard sends (POST, JSON):
{ "event": "lookup", "kind": "order", "reference": "A-1042",
  "email": null, "phone": "+447700900123" }

// Custard sends when a customer confirmed a change:
{ "event": "action", "action": "change_address", "reference": "A-1042",
  "details": "New address: 12 Rue Cler, Paris", "phone": "+33…",
  "conversation_id": "7c1f…" }

// Answer with any short JSON (or 404 when nothing matches):
{ "status": "shipped", "eta": "Friday" }

Your secret arrives as Authorization: Bearer … and X-Custard-Secret. Answer within 3.5 seconds for lookups and 5 seconds for changes. With Verify identity on, your staff confirms who the customer is with a code first.

Stuck?

Write to hello@custardai.dev and we will help you connect it.