Docs
Documentation

Live checkout

Let the chat fill your own cart and follow the order your checkout creates.

On this page: What it is · The customer's journey · Set it up · 1. The checkout link · 2. The order message · Code to copy · Statuses · Test it · Security

What it is

With Live checkout on your site, the AI Sales Agent helps the customer choose and builds the basket in the chat, then hands it to your own checkout. Your site asks the customer to log in if it needs to, takes the payment with your own gateway (or cash on delivery) and creates the order as it always does. Your site then tells Central AI, and the customer’s chat shows the order and every change within seconds.

Two small pieces on your side, nothing else: a checkout link that fills your cart, and one signed message to Central AI when an order is placed or changes. No Commerce API, no keys to publish, no new login. The customer’s details come from your own checkout, never from the chat.

The customer's journey

  1. The customer chats on your website and chooses, say, a course. The chat shows it in the basket.
  2. They press Checkout on yoursite.com. Your checkout opens in a new tab, with the basket in your cart and a chat reference your site keeps with the order. The chat shows Waiting for your order on the website…
  3. They log in or sign up on your site if it asks, and pay, or choose cash on delivery.
  4. Your site sends Central AI a signed message: order placed (and paid). The chat shows Payment received or Order placed with your order number.
  5. Later changes you send (confirmed, completed, cancelled, refunded) appear in the chat the same way. On Messenger, Instagram and WhatsApp the customer gets a message instead.

Set it up

  1. Give each offering the id it has on your site: the id in the offerings feed, or the SKU. That id is what the checkout link sends.
  2. In AI Sales Agent, section 3, set the kinds you sell on your site to Live checkout on your site, and enter your checkout link (below). Choose what a Messenger, Instagram or WhatsApp customer gets: the checkout link, or an order taken in the chat.
  3. In the same box in section 3, press Generate the secret and address under Order updates from your site. Copy the address and the secret for your developer. The secret is shown once.
  4. Your developer adds the checkout route and the order message (code below).
  5. Publish, then test it in the Playground.

A link on your site that Central AI fills in for each checkout. It must be https:// and contain {items} and {ref}:

https://yourshop.com/cart/add?items={items}&ref={ref}
  • {items} becomes CODE:QTY,CODE:QTY, for example WEB-101:1,UX-7:2, where each code is the offering’s id on your site, URL-encoded. Codes containing , or : are not supported. At most 50 lines.
  • {ref} becomes the chat’s reference, such as cai_Z3xH1…. Keep it with the cart and then with the order: it is how the order finds its chat. One reference, one order: once an order has come back under it, a second order under the same reference reaches no chat, and neither does one sent after the link expired (7 days).
  • Put {items} and {ref} in the path or the query, never in the site’s address.

Your route adds the items to the customer’s own cart (their session on your site), keeps the reference, and sends them to your checkout. If your checkout requires an account, it asks them to log in there, as it does today.

2. The order message

When an order with a chat reference is placed, and whenever it changes, your server sends one POST to the address shown in section 3, under Live checkout, for example http://centralai.daffodil-bd.com/webhooks/orders/ow_EXAMPLE/:

POST /webhooks/orders/ow_EXAMPLE/
Content-Type: application/json
Central-AI-Signature: t=1791009400,v1=5f2c…

{
  "event_id": "ENR-2041-paid",
  "occurred_at": "2026-10-01T10:12:04Z",
  "ref": "cai_Z3xH1…",
  "order": {
    "reference": "ENR-2041",
    "status": "confirmed",
    "payment_status": "paid",
    "total": {"amount": "12500.00", "currency": "BDT"},
    "payment_method": {"label": "SSLCommerz", "collected_offline": false},
    "items": [{"title": "Full-Stack Web Development", "quantity": 1}]
  }
}
FieldRequiredMeaning
event_idyesUnique per change. Send the same one again when you retry the same change: Central AI applies it once.
occurred_atyesWhen the change happened, UTC, ending in Z, no more than 5 minutes ahead of Central AI’s clock. A message older than the last one applied changes nothing.
refyes, to reach the chatThe chat reference from the checkout link. Without it Central AI answers unmatched and creates no order: nothing reaches a chat or Requests and orders.
order.referenceyesYour order number, as the customer sees it: one line of up to 64 letters, digits, spaces and # . _ / : -.
order.statusnoplaced, confirmed, completed, cancelled, canceled, rejected.
order.payment_statusnounknown, not_required, unpaid, pending, authorized, partially_paid, paid, partially_refunded, refunded, voided, failed.
order.fulfilment_statusnonot_started, pending, partial, done, failed.
order.totalnoYour total, {"amount": "12500.00", "currency": "BDT"}: a decimal string, never a number.
order.payment_methodno{"label": "Cash on delivery", "collected_offline": true} for cash on delivery; false for an online gateway.
order.itemsno[{"title": "…", "quantity": 1}]. Without it, the basket from the chat is shown.

The signature. Central-AI-Signature: t=<Unix seconds>,v1=<hex>, where the hex is HMAC-SHA256, keyed with your secret, of the timestamp, a dot, and the raw body exactly as sent. Central AI refuses a timestamp more than 5 minutes from its own clock, and bodies over 64 KB. After a rotation the old secret keeps working for 24 hours.

Answers. 200 with {"received": true, "outcome": "applied"} (or unmatched, duplicate, stale, conflict for a second order under one reference); 403 when the address or the signature is wrong; 400 for a malformed body; 413 for a body over 64 KB; 422 for a signed body that cannot be recorded (do not retry it unchanged); 429 when too many arrive at once, with a Retry-After header when it comes before the signature check. Retry a failure with the same event_id.

Code to copy

PHP: the checkout route

// https://yourshop.com/cart/add?items={items}&ref={ref}
session_start();
foreach (explode(',', $_GET['items'] ?? '') as $pair) {
    [$code, $qty] = array_pad(explode(':', $pair, 2), 2, '1');
    if ($code !== '') {
        cart_add($code, max(1, (int) $qty));          // your own cart function
    }
}
$ref = $_GET['ref'] ?? '';
$_SESSION['central_ai_ref'] = preg_match('/^cai_[A-Za-z0-9_-]{10,60}$/', $ref) ? $ref : '';
header('Location: /checkout');                          // login, if your checkout needs it, happens there
// When the order is created, save $_SESSION['central_ai_ref'] on it.

PHP: the order message

function central_ai_order_update(string $ref, string $eventId, array $order): void {
    $body = json_encode([
        'event_id'    => $eventId,                      // e.g. "ENR-2041-paid"; the same on a retry
        'occurred_at' => gmdate('Y-m-d\TH:i:s\Z'),
        'ref'         => $ref,
        'order'       => $order,
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    $t = time();
    $signature = hash_hmac('sha256', $t . '.' . $body, getenv('CENTRAL_AI_WEBHOOK_SECRET'));
    $ch = curl_init(getenv('CENTRAL_AI_WEBHOOK_URL'));
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $body,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', "Central-AI-Signature: t=$t,v1=$signature"],
    ]);
    curl_exec($ch);
    curl_close($ch);
}

// After your payment gateway confirms (its IPN or callback):
central_ai_order_update($order->central_ai_ref, $order->number . '-paid', [
    'reference' => $order->number,
    'status' => 'confirmed',
    'payment_status' => 'paid',
    'total' => ['amount' => number_format($order->total, 2, '.', ''), 'currency' => 'BDT'],
]);

Node.js (18 or later): the order message

const crypto = require('crypto');

async function centralAiOrderUpdate(ref, eventId, order) {
  const body = JSON.stringify({ event_id: eventId, occurred_at: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'), ref, order });
  const t = Math.floor(Date.now() / 1000);
  const signature = crypto.createHmac('sha256', process.env.CENTRAL_AI_WEBHOOK_SECRET).update(`${t}.${body}`).digest('hex');
  await fetch(process.env.CENTRAL_AI_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Central-AI-Signature': `t=${t},v1=${signature}` },
    body,
  });
}

Python: the order message

import hashlib, hmac, json, os, time, urllib.request
from datetime import datetime, timezone

def central_ai_order_update(ref, event_id, order):
    body = json.dumps({'event_id': event_id, 'ref': ref, 'order': order,
                       'occurred_at': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}).encode()
    t = int(time.time())
    signature = hmac.new(os.environ['CENTRAL_AI_WEBHOOK_SECRET'].encode(), f'{t}.'.encode() + body,
                         hashlib.sha256).hexdigest()
    request = urllib.request.Request(os.environ['CENTRAL_AI_WEBHOOK_URL'], data=body, method='POST', headers={
        'Content-Type': 'application/json', 'Central-AI-Signature': f't={t},v1={signature}'})
    urllib.request.urlopen(request, timeout=10)

Statuses

You sendThe customer's chat shows
"status": "placed", cash on deliveryOrder placed. You pay on delivery.
"status": "placed"Order placed. The business will confirm it.
"payment_status": "paid"Payment received.
"status": "confirmed"Confirmed by the business.
"status": "completed"Completed.
"status": "cancelled"This request was cancelled.
"status": "rejected"The business could not accept this request.
"payment_status": "failed"The payment did not go through.

These orders also appear in Requests and orders, read-only: your own system is where they are managed.

Test it

  1. Open the Playground (Agent & Chat → Playground) and ask for something you sell on your site. Say you want to buy it.
  2. Press Checkout on yoursite.com. Your site should open with the item in the cart. Check that your route kept the ref.
  3. Place the order. Your server sends the order message; the Playground shows the order within seconds, and the Live checkout box in section 3 shows the time and result of the last message.

Security

  • Payment is never taken from the browser. Only your server’s signed message can say an order is placed or paid; a customer opening your thank-you page proves nothing.
  • Keep the secret on your server. Never put it in a web page or an app. If it may have leaked, press Rotate the secret in section 3 (Live checkout), then Stop the old secret now: otherwise the old one keeps working for 24 hours.
  • Orders join chats only by the reference, never by email or phone. References are random and belong to your business only.
  • The chat never places an order. It fills the basket; the customer places the order on your checkout.
  • Only a company admin changes where customers pay. The checkout link, the webhook secret and the payment settings can be changed only by an admin of your company (or a Central AI platform admin); other team members see them read-only.

Signed in? Your own keys, endpoint addresses and the full error tables are on the API reference inside your dashboard. These public pages never show anyone’s key: examples use placeholders such as YOUR_EXTERNAL_API_KEY.