Route DeskClerk human handoffs into your Slack workspace
1. Who this page is for
Your support team lives in Slack and wants to know the moment a website visitor asks for a human.
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
conversationIdper Slack thread and the AI remembers what was said before. - A handoff signal the platform computes for you: the reply carries an
escalationobject when a human should take over, so you do not have to write detection rules. - One API for every channel. Adding Slack does not change your DeskClerk setup.
You do not get
- We do not run inside Slack. There is no Slack app, bot, or connector from us, and no marketplace listing.
- We do not receive or send Slack 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:writescope. 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
conversationIdper user thread. - The language code you will send as
locale. It has to be one of the languages enabled for your site (for exampleenorzh), written exactly as your site settings show it. - Your Slack messaging backend. Sending and receiving messages in Slack stays on your side; DeskClerk is the answering engine you call.
4. Step by step
- Understand what the platform does on its own: when a visitor asks for a human on your website, the conversation is marked for handoff in your DeskClerk workspace and the site owner receives an email notification. Nothing is sent to Slack by DeskClerk.
- Create an Incoming Webhook in your Slack workspace (Slack admin → Apps → Incoming Webhooks) and keep the webhook URL on your server.
- On your side, call the message endpoint as described in section 5 and inspect the
escalationobject in the response. - When
escalationis present, POST a short message to your Slack webhook with the visitor's text and the conversation ID. - Keep the mapping between the DeskClerk
conversationIdand your Slack thread so an agent can pick up the right person.
There is no Slack app and no Slack channel push built into DeskClerk. The bridge in this guide runs on your servers, between DeskClerk's API and Slack's Incoming Webhook.
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-slack-12025550123-1" \
-d '{"channel":"slack","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-slack-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("slack", "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 exactlyX-Integration-Api-Key(orAuthorization: Bearer <key>), that the key isACTIVE, 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 theRetry-Afterheader, 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 allowhttps://chat.example.cominscript-srcandconnect-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
400withLocale must be enabled for this site's Help Center.localemust be one of the language codes enabled for your site (for exampleenorzh) — not a region form such asen-USorzh-CN. Check the language list in your site settings and send the code exactly as it appears there. -
aiMessageisnullbut 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
404on the message call. Almost always a wrongconversationId— it must be thedata.idyou 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
codefrom the response body, and theIdempotency-Keyyou 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.