Silknet

Knowledge Base Admin

Sign in with an admin account to manage the chatbot configuration.

or
Silknet
Admin Console
Organization
Ready
ContainAI only Silknet cannot see or edit this. Must contain {{KNOWLEDGE_BASE}} — that is where the knowledge base is substituted in at request time.
Matches the live version
0 / 0
Quick chat — try a single question against the current draft
Results
Run a question set to see answers here.

Analytics

—
Total Chats
—
Messages
—
Avg. Response Time
—
Handed to an Agent
—
Total charged
Questions per chatChatsPrice per chatTotal

Users

API documentation

The chatbot answers customer messages from Freshchat. You send us the conversation, we send back the reply.

How it works

The exchange is asynchronous. You send us a question and we acknowledge it immediately; the answer arrives a few seconds later as a separate call to your webhook. We do not hold the connection open while the model is working.

you ──── POST question ────▶ chatbot 202 Accepted, straight away you ◀──── POST answer ───── chatbot separate call, a few seconds later

Send the full conversation every time, oldest message first. The last message is the one to answer; everything before it is context the bot needs for follow-up questions like “and how much is that?”.

Sending a question

POST https://silknet.containai.io/api/v1/freshchat/chat
X-API-Key: <your inbound key>
Content-Type: application/json
{
  "conversation_id": "a15c3f78-959e-4d42-bd43-bd3b059300fa",
  "contents": [
    {
      "parts": [
        { "text": "user said: გამარჯობა" },
        { "text": "agent said: გამარჯობა! რით შემიძლია დაგეხმაროთ?" },
        { "text": "user said: რა ღირს ულიმიტო ინტერნეტი?" }
      ]
    }
  ]
}
FieldRequiredNotes
conversation_idyesThe Freshchat conversation id. Any non-empty string.
contents[0].parts[]yesThe conversation so far, oldest first. One entry per message.
categorynoThe category we sent you earlier in this chat, echoed back.
subcategorynoThe subcategory we sent you earlier, echoed back.

Prefix each message with user said: or agent said: so we know who is speaking. A message with neither prefix is treated as coming from the customer.

What comes back

202 Accepted
{ "status": "accepted", "conversation_id": "a15c3f78-..." }

202 means “received, we will call you back”. It is not the answer.

StatusMeaning
202Accepted — the answer will arrive at your webhook
400Malformed body: missing conversation_id, empty contents, or no usable messages
401Missing or wrong X-API-Key
500Server misconfiguration on our side

Receiving the answer

We POST to the webhook URL you gave us:

POST <your webhook>
apikey: <your outbound key>
Content-Type: application/json
{
  "conversation_id": "a15c3f78-959e-4d42-bd43-bd3b059300fa",
  "answer": "ულიმიტო ინტერნეტი 30 დღით ღირს 35 ლარი.",
  "answer_number": 3,
  "command": null
}
FieldPresentMeaning
conversation_idalwaysThe id you sent us
answeralwaysThe text to show the customer
answer_numberalwaysWhich question this answers — 1, 2, 3…
commandalwaysnull, "transfer_to_operator" or "end_chat"
categorysometimesTicket category of the inquiry
subcategorysometimesTicket subcategory of the inquiry

answer_number counts questions, not messages. The third message in a chat is usually the second question.

The command field

command is on every answer and tells you what to do with the chat.

ValueMeaningWhat to do
nullAn ordinary answerPost answer to the customer
"transfer_to_operator"The chat needs a person — the bot could not answer, the customer asked for one, the chat has passed 20 questions, or the request failed outright on our sidePost answer, then route the chat to an operator
"end_chat"Two causes, one command: the customer keeps writing about things unrelated to Silknet after being told once that we only cover Silknet topics, or sends a second offensive message after being asked once to stop. The second has nothing to do with the topic, so a chat can close on someone who never went off-topic.Post answer, then close the conversation. Nobody is waiting for an operator.

Route on command, never on the text of answer. The wording changes with the customer’s language and with the reason — someone who asks for an operator is not told the bot could not find an answer. command is a fixed value and does not vary.

Ticket category

Some answers carry two extra fields classifying the inquiry. The values come from the Ticket: labels in the knowledge base and are sent verbatim, so they can be written straight into a ticket.

"category":    "მობილური/საინფორმაციო ტარიფები/პირობები/აქციები",
"subcategory": "eSIM მომსახურება"
  • Both are null when the message has no topic — most often a bare greeting.
  • They do not always arrive on answer 1. If the chat opens with a greeting there is nothing to classify, so we try again on the next question, and on the one after that.
  • On answers where we did not classify, we send back the category you last gave us — so every answer carries the current category, not a change to it.

category = $json.category applied on every answer is always correct. There is no accumulation rule to implement, and no answer can clear a category you already hold — including one an operator set by hand, which we echo untouched and never check against our own list.

Still send it back to us on later requests: that tells us the inquiry is already classified so we can stop. If you never send it back, nothing breaks — we simply try on each of the first three questions.

A chat closed with end_chat carries ბოტი/საუბარი არ შედგა with subcategory სხვაგან მოხვდა/არამიზნობრივი მომართვა — but only when the conversation never had a real category. A customer who asked a genuine question and then went off-topic, or turned abusive, keeps the category their question produced.

Both closes file that same label — the off-topic one and the abuse one. Silknet chose it in their own document, which is why an abuse close lands under a subcategory that reads “landed elsewhere / non-targeted inquiry”. Counting that subcategory therefore counts off-topic closes and abuse closes together, and nothing in the payload separates them.

Language

The bot replies in the language the customer writes in — Georgian, Russian or English. Nothing in this API changes: the same fields, the same shapes.

Detection reads the customer’s own messages and needs nothing from you. Georgian typed in Latin letters (“gamarjoba, ra girs paketi?”) is treated as Georgian, not English. Georgian is used whenever the evidence is unclear.

Two things stay Georgian in every language, deliberately:

  • category and subcategory — labels for your ticketing system, not text for the customer.
  • Package, service and brand names — a Russian reply gives the price and dial code in Russian but keeps the package name as it appears in your own systems, so the customer can match it on the USSD menu.

Prices, currency and USSD codes are reproduced character for character in every language.

Delivery and retries

Acknowledge quickly

Respond as soon as you have received the request — not when your workflow has finished posting to Freshchat. A reply taking longer than 10 seconds counts as a failed delivery and we retry, up to 3 attempts, waiting 1s, then 4s, then 16s.

In n8n this is the Webhook node’s Respond setting: choose Immediately, not When Last Node Finishes.

Removing duplicate answers

conversation_id + answer_number uniquely identifies one answer and is identical on every retry of it. Use the pair to drop repeats:

Remove Duplicates node
  Keep Items Where:    Value Is New
  Value to Dedupe On:  {{ $json.conversation_id }}-{{ $json.answer_number }}

Both fields must be in the key. Deduplicating on conversation_id alone suppresses every answer after the first — the customer gets one reply and then silence.

Examples

A first question, classified

{
  "conversation_id": "ex-A",
  "answer": "eSIM-ის საფასური მიმდინარე აქციით 5 ₾-ია...",
  "answer_number": 1,
  "command": null,
  "category": "მობილური/საინფორმაციო ტარიფები/პირობები/აქციები",
  "subcategory": "eSIM მომსახურება"
}

The chat opens with a greeting

{
  "conversation_id": "ex-B",
  "answer": "გამარჯობა! მე სილქნეტის ციფრული ასისტენტი ვარ. ამჟამად სატესტო რეჟიმში ვმუშაობ და მზად ვარ, დაგეხმაროთ.",
  "answer_number": 1,
  "command": null,
  "category": null,
  "subcategory": null
}

A greeting has no topic, so both are null. The keys are still present.

A follow-up question

{
  "conversation_id": "ex-C",
  "answer": "ულიმიტო ინტერნეტ პაკეტის გასააქტიურებლად აკრიფეთ *019#...",
  "answer_number": 2,
  "command": null,
  "category": "მობილური/საინფორმაციო ტარიფები/პირობები/აქციები",
  "subcategory": "eSIM მომსახურება"
}

answer_number is 2 — the second question, not the third message. We did not reclassify here: the category is the one from answer 1, sent back unchanged.

The chat needs a person

{
  "conversation_id": "ex-D",
  "answer": "გასაგებია. გადაგამისამართებთ ოპერატორთან, გთხოვთ დაელოდოთ.",
  "answer_number": 1,
  "command": "transfer_to_operator",
  "category": null,
  "subcategory": null
}

The chat is closed

{
  "conversation_id": "ex-E",
  "answer": "აღნიშნულ საკითხზე დახმარებას ვერ გაგიწევთ. ვინაიდან თქვენი მოთხოვნა სილქნეტის სერვისებს არ უკავშირდება.",
  "answer_number": 3,
  "command": "end_chat",
  "category": "ბოტი/საუბარი არ შედგა",
  "subcategory": "სხვაგან მოხვდა/არამიზნობრივი მომართვა"
}

Something failed on our side

If something breaks after we have already acknowledged, we still call you rather than leaving the customer in silence. The payload is the same shape as a normal transfer, so there is nothing different to do:

{
  "conversation_id": "ex-F",
  "answer": "ამ საკითხის დასაზუსტებლად ოპერატორთან დაგაკავშირებთ.",
  "answer_number": 1,
  "command": "transfer_to_operator",
  "category": null,
  "subcategory": null
}

One other message is worth recognising: if the bot cannot produce a reply at all, answer is ბოდიში, დაფიქსირდა ტექნიკური ხარვეზი, სცადეთ ხელახლა and the customer is asked to try again.

That message carries command: null. The two failure paths differ. A request that fails outright — a configuration problem, an unexpected error — is escalated for you: the answer is the operator sentence and command is "transfer_to_operator". A request where the model could not produce a usable reply returns the sentence above with no command, because nothing asked for a person.

This is the one case where command alone does not tell you what to do, which is why the sentence is fixed and safe to match on. Whether it should escalate instead is a policy choice.

Keys

Two separate keys, not interchangeable:

KeyDirectionSent as
Inboundyou → chatbotX-API-Key header
Outboundchatbot → youapikey header