Connect your systems

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 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…" }

// Custard sends when the founder asks a business question, e.g. "how much have we made today?":
{ "event": "metrics", "kind": "metrics", "metric": "revenue", "period": "today",
  "requested_by": "owner" }
// period is today, yesterday, this_week, last_7_days, this_month or last_30_days.
// metric is revenue, transactions, payouts, balances, customers or any name you support.
// "how many customers / users / sign-ups do we have?" always arrives as metric "customers".

// Answer with any short JSON (or 404 when nothing matches):
{ "status": "shipped", "eta": "Friday" }
{ "revenue": 1450000, "currency": "NGN", "count": 320 }

// Answer for a signed-in customer's account (kind "account", user_id attached from setContext).
// Include their last 1 to 3 transactions so staff answer "I sent money, where is it?" at once:
{ "found": true, "summary": "Tier 2 account, active",
  "details": { "account_number": "0123456789", "bank": "Partner Bank", "tier": 2,
    "balances": { "NGN": 125000 },
    "recent_transactions": [
      { "id": "TX-8841", "type": "transfer_out", "amount": 50000, "currency": "NGN",
        "status": "pending", "session_id": "000013…", "recipient": "Kofi Mensah",
        "created_at": "2026-11-04T09:12:00Z" }
    ] } }

// Answer for metric "customers" (counts only, null for anything you don't track):
{ "metric": "customers", "period": "today", "total_customers": 15230,
  "new_in_period": 42, "verified_customers": 11870, "active_in_period": 3105 }

// A metric you don't support: HTTP 400
{ "error": "unsupported_metric_or_period" }

Your secret arrives as Authorization: Bearer … and X-Custard-Secret. Answer within 3.5 seconds for lookups and 5 seconds for changes. The same link also answers the founder's own questions, like "how much have we made today?": those arrive as event: "metrics" with a metric and period, read only, and only owners and managers can ask. With Verify identity on, your staff confirms who the customer is with a code first.

Connect your systems

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.

Send sales to your accounting app

The same link also gets quote.accepted when a customer says yes to a quote and quote.paid when it is marked paid. In Zapier or Make, add a step that creates the invoice in QuickBooks, Xero or FreshBooks on accepted and records the payment on paid. Use id to skip repeats. Tax and bookkeeping stay in your accounting app.

{
  "event": "quote.paid",
  "id": "quote.paid:8f3a…",
  "quote_id": "8f3a…",
  "currency": "USD",
  "total_cents": 125000,
  "deposit_cents": 25000,
  "amount_paid_cents": 25000,
  "balance_due_cents": 100000,
  "items": [{ "name": "Deep office clean", "quantity": 1, "unit_cents": 100000, "total_cents": 100000 }],
  "customer": { "id": "c41d…", "name": "Elena Rossi", "email": "elena@example.com", "phone": null },
  "accepted_at": "2026-11-03T18:15:00Z",
  "paid_at": "2026-11-03T18:22:00Z"
}

Support cases with your own team

When staff open a support case, the same link gets case.opened, so your helpdesk can create a ticket. When the case is solved, by the AI, your dashboard or your own team, it gets case.resolved with the resolution note. Use it for your own in-app push.

{
  "event": "case.resolved",
  "case": {
    "id": "5b1e…",
    "number": 1042,
    "subject": "Missing transfer",
    "details": "Sent 50,000 on Monday, not received. Session ID 0000…",
    "kind": "support",
    "priority": "high",
    "status": "solved",
    "conversation_id": "c0a1…",
    "opened_at": "2026-11-03T09:12:00Z",
    "resolved_at": "2026-11-03T14:40:00Z"
  },
  "customer": { "name": "Chidi Okafor", "email": "chidi@example.com", "phone": null },
  "note": "Reversed 50,000 to wallet. Ref: REV-88219"
}

When your team solves, waits on or reopens a ticket in your own helpdesk, tell Custard AI from your server with your API key. status is solved, waiting or open; a note on its own adds an update. Errors: 401 invalid_api_key, 400 missing_case, invalid_status or nothing_to_change, 404 case_not_found.

POST https://custardai.dev/api/public/v1/cases
Authorization: Bearer csk_…
Content-Type: application/json

{ "case_number": 1042, "status": "solved", "note": "Reversed 50,000 to wallet. Ref: REV-88219" }

→ 200 { "ok": true, "case_number": 1042, "status": "solved" }

On solve, the customer gets an email with the note. In your website chat, a visitor sees their own open or recently solved case at the top, and staff greet them knowing it. Cases only show on the browser where that visitor chatted, so nobody can look up another person's case.

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".

Show support what the screen looks like

Call setDiagnostics whenever the visitor's screen changes. Staff see it as facts and can explain exactly why a button is blocked, and any support case they open carries the snapshot for your developers. Send semantic state only, never form values, card numbers or screenshots. The React, React Native and Flutter packages have the same setDiagnostics.

window.CustardWidget.setDiagnostics({
  current_screen: "Withdraw",
  cta_state: "disabled",
  disabled_reasons: ["KYC pending"],
  app_version: "2.4.1",
  platform: "ios",
  network_status: "online",
  last_ui_error: "Daily limit exceeded",
  details: { tier: "basic" }
});

Check it really came from us

Tap Show secret under Send call summaries. Once your signing secret exists (it is created automatically when the server key is set), every delivery has an X-Custard-Signature header. If the header is missing, treat the delivery as unsigned and reject it. Failed deliveries (no answer, 429 or 5xx) are retried twice, about 1 and 3 seconds apart, with the same body, so make your handler safe to receive the same message more than once. The signature is: 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));
}

Connect your systems

Connect your apps (MCP)

Many apps now offer an MCP link: shops, CRMs, help desks, databases and your own backend. In Rules & connections, open Connected apps, give it a name, paste the https link and an access token if it needs one, and tap Connect app.

Custard AI reads the app's tool list. Tools the app marks as read only are switched on; anything that changes data stays off until you switch it on, and always waits for the customer's clear yes before it runs. You can make read-only tools ask first too. Then switch on Use connected apps for the staff member in Skills.

Your staff use these tools on calls, website chat and voice, WhatsApp and email. Answers from the app are treated as information, never instructions. The token stays on our servers and is never shown in the browser. Up to 5 apps, 40 tools each; links must be https and public.

Connect your systems

Answer support emails

In Rules & connections, open Email support and tap Turn on. You get a private forwarding address. Add a forwarding rule in Gmail, Outlook or your email host from your support address to it. Nothing else to install.

Each email becomes a conversation. Your staff reads only the new part (quoted history and signatures are dropped), replies with your business name, and the customer's reply lands back in the same thread. Support cases, bookings, live lookups and your shared rules all work exactly as on calls and chat, and the conversation webhook fires like any other channel.

Choose Send on its own or I approve every reply. Either way, emails about refunds, passwords, payment details, closing an account or legal action always wait as a draft in Needs a human, where you edit and tap Send reply. Out of office replies, bounces, mailing lists and no-reply senders are ignored so mail never loops.

Stuck?

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