Use DeskClerk AI answers behind WhatsApp

Use DeskClerk AI answers behind WhatsApp

1. Who this page is for

You already own a WhatsApp Business setup (or are building one) and want the same AI that answers on your website to answer your WhatsApp messages through your own backend.

2. What you get, and what you do not get

You get

  • The same AI answers that run on your website, exposed as two simple HTTPS endpoints your own code can call.
  • Conversation memory: keep one conversationId per WhatsApp thread and the AI remembers what was said before.
  • A handoff signal the platform computes for you: the reply carries an escalation object when a human should take over, so you do not have to write detection rules.
  • One API for every channel. Adding WhatsApp does not change your DeskClerk setup.

You do not get

  • We do not run inside WhatsApp. There is no WhatsApp app, bot, or connector from us, and no marketplace listing.
  • We do not receive or send WhatsApp messages. Your own backend (or the platform you use for that channel) keeps doing that job.
  • We do not know your channel's user IDs. You pass your own external_user_id, which only has to be stable on your side.
  • Usage counts against your plan's monthly quota, per answered message. When you need more, you can top up at any time.

3. Before you start

  • A DeskClerk site that has been published at least once. Without a publish the AI has nothing to answer from.
  • Your Site ID (a UUID, for example 7b0c9f5e-2a8d-4f31-8e6b-1c2d3e4f5a6b). It is shown in your workspace under the site settings.
  • An API key with the chat:write scope. Create it in your workspace and keep it on your server only — never in a mobile app or in front-end JavaScript.
  • A server that can call HTTPS endpoints, plus a place to store one conversationId per user thread.
  • The language code you will send as locale. It has to be one of the languages enabled for your site (for example en or zh), written exactly as your site settings show it.
  • Your WhatsApp messaging backend. Sending and receiving messages in WhatsApp stays on your side; DeskClerk is the answering engine you call.

4. Step by step

  1. Keep your WhatsApp messaging backend (the service that receives Meta webhooks and sends replies). DeskClerk does not receive WhatsApp messages.
  2. When a message arrives, your backend creates one DeskClerk conversation per WhatsApp thread and stores the returned conversationId next to your thread ID.
  3. For every new inbound message, post the raw text to the message endpoint and show aiMessage.content as the reply.
  4. If the response has no aiMessage, or has an escalation object, stop answering and route the thread to a human on your side.
  5. Keep the same conversationId for follow-up messages in that thread so the AI remembers the context.

The two steps below are the whole integration: create a conversation, then post each message. Nothing else is required, and the API only ever grows — what you write today keeps working.

5. Copy-paste example

Set these three values once, then the rest is boilerplate:

Variable Example value Where it comes from
BASE_URL https://chat.example.com The host that serves your widget and help center
SITE_ID 7b0c9f5e-2a8d-4f31-8e6b-1c2d3e4f5a6b Workspace → site settings
DESKCLERK_API_KEY your key Workspace → API keys (chat:write scope)

Step 1 — create the conversation (once per thread):

curl -sS -X POST "$BASE_URL/api/v1/integrations/sites/$SITE_ID/conversations" \
  -H "X-Integration-Api-Key: $DESKCLERK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: conv-whatsapp-12025550123-1" \
  -d '{"channel":"whatsapp","external_user_id":"12025550123","user_name":"Sam","locale":"en","timezone":"America/New_York"}'

The response contains data.id — that is your conversationId. Store it next to your own thread ID. The example UUID for a stored conversation is 0f4c2a91-5d63-4b8e-9f27-3a1b6c8d5e40.

Step 2 — send each message and show the answer:

curl -sS -X POST "$BASE_URL/api/v1/integrations/sites/$SITE_ID/conversations/$CONVERSATION_ID/messages" \
  -H "X-Integration-Api-Key: $DESKCLERK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: msg-whatsapp-12025550123-1" \
  -d '{"content":"Do you ship to Canada?"}'

A complete Node.js handler, including both branches you must handle:

const BASE_URL = "https://chat.example.com";
const SITE_ID = "7b0c9f5e-2a8d-4f31-8e6b-1c2d3e4f5a6b";
const API_KEY = process.env.DESKCLERK_API_KEY;

async function reply(channel, externalUserId, locale, text) {
  const convRes = await fetch(`${BASE_URL}/api/v1/integrations/sites/${SITE_ID}/conversations`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Integration-Api-Key": API_KEY,
      "Idempotency-Key": `conv-${channel}-${externalUserId}-${Date.now()}`
    },
    body: JSON.stringify({
      channel,
      external_user_id: externalUserId,
      locale,
      timezone: "America/New_York"
    })
  });
  const conv = await convRes.json();
  if (conv.code !== 0) {
    throw new Error(`create conversation failed: ${conv.code} ${conv.message}`);
  }
  const conversationId = conv.data.id;

  const msgRes = await fetch(
    `${BASE_URL}/api/v1/integrations/sites/${SITE_ID}/conversations/${conversationId}/messages`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Integration-Api-Key": API_KEY,
        "Idempotency-Key": `msg-${conversationId}-${Date.now()}`
      },
      body: JSON.stringify({ content: text })
    }
  );
  const msg = await msgRes.json();
  if (msg.code !== 0) {
    throw new Error(`send message failed: ${msg.code} ${msg.message}`);
  }

  // No AI answer this turn: hand the thread to a human on your side.
  if (!msg.data.aiMessage) {
    return { conversationId, text: "A teammate will reply here shortly.", escalate: true };
  }

  // The platform decides when a human should take over.
  const escalate = Boolean(msg.data.escalation);
  return { conversationId, text: msg.data.aiMessage.content, escalate };
}

reply("whatsapp", "12025550123", "en", "Do you ship to Canada?").then(console.log);

Each message call is synchronous and can take up to 45 seconds, so give your HTTP client a timeout above that. If you need to show words as they arrive, use the streaming twin endpoint .../messages/stream instead; the request body is the same.

6. Common questions

  • I get 403 Forbidden / code 40301. The key is missing, disabled, or does not carry the scope the endpoint needs. Check that the header name is exactly X-Integration-Api-Key (or Authorization: Bearer <key>), that the key is ACTIVE, and that this Site ID is listed on the key.

  • I get 429 Too Many Requests / code 42901. You are above the request rate for the site. Read the Retry-After header, wait that many seconds, then retry with backoff. Do not retry in a tight loop.

  • The bubble does not appear on the page. View the page source and search for widget.js — if it is missing, the snippet is not on the rendered page (a cache plugin or a theme setting is dropping it). If it is present, check the browser console for a Content-Security-Policy error and allow https://chat.example.com in script-src and connect-src. Ad blockers also hide chat bubbles; test in a clean profile.

  • The bubble appears but the answer says it does not know. The knowledge is empty, still processing, or tagged with a different locale. In your workspace open Site → Knowledge and confirm the item count is above zero, the pending count is zero, and the item locale matches how visitors ask.

  • Creating a conversation returns 400 with Locale must be enabled for this site's Help Center. locale must be one of the language codes enabled for your site (for example en or zh) — not a region form such as en-US or zh-CN. Check the language list in your site settings and send the code exactly as it appears there.

  • aiMessage is null but the call succeeded. That is not an error: it means the site's assistant did not answer this turn (for example the assistant is paused for that conversation). Show your own fallback text and route the thread to a human.

  • I get 404 on the message call. Almost always a wrong conversationId — it must be the data.id you stored, a UUID, not your own thread ID. A malformed Site ID returns the same status.

  • What is sources[].score? A retrieval similarity between 0 and 1, useful for a "show the reference" UI. It is not an accuracy score.

  • Should I send the conversation history again? No. Send only the new message; the platform keeps the context for the conversationId.

7. Who to contact

  • Product and setup questions: [email protected]
  • Include in every message: your Site ID, the exact request (method and path), the HTTP status, the code from the response body, and the Idempotency-Key you used. That is everything we need to find the request in our logs.
  • Security: never send us your API key in plain text. The key prefix (the first characters) is enough for us to locate it.