Documentation

Integrate Znatoka

Add contact-safe buyer↔seller dispute chat to your platform over a REST API + HMAC webhook. Fastest path: hand the spec to your AI coder below.

For AI coders

Hand this to Cursor, Claude Code, or Windsurf

Paste one URL and your AI coder fetches the entire integration spec — endpoints, webhook, HMAC, SDK. No PDF, no login.

Full spec (LLM-ready)
OpenAPI 3.1 (JSON)
Ready-to-paste prompt
You are integrating Znatoka (contact-safe buyer<->seller dispute chat) into my app.

Read the full API + webhook spec here: https://znatoka.com/llms.txt
OpenAPI 3.1 JSON: https://znatoka.com/openapi.json
Official SDK: npm package "chatdisiniaja" (Node/TypeScript).

Requirements:
- Keep CHATDISINIAJA_API_KEY server-side only (never expose to the browser).
- When a dispute/refund starts, POST /api/v1/conversations to get an id plus
  buyer_url/seller_url (or the ready buyer_embed/seller_embed <script> snippets).
- Deliver the correct link/snippet to each party.
- Webhook is OPTIONAL: the embed/SSE chat works without it. Add one only to notify
  a party who isn't viewing the chat — verify the X-Chatdisiniaja-Signature header
  (HMAC-SHA256 over the raw body) and notify notify_role via open_conversation_url.
  Otherwise rely on the embed widget or poll GET /api/v1/conversations/:id.
- Close the conversation when the dispute is resolved.

Follow the spec exactly. Do not invent endpoints or fields.
Get your api_key & webhook_secret by signing up free. Manage them anytime in the Portal.

Easiest way — embed widget

No chat UI to build. Create a conversation (step 1) to get a token for each party, then paste this single lineon your platform's page — a floating chat bubble appears (like Tawk.to), with no contact leaking:

<script src="https://znatoka.com/embed.js"
        data-token="<this_party_token>" async></script>

The token is in buyer_url/seller_url (the part after /c/), or use the buyer_embed/seller_embedfield from the create response directly — it's already a ready-to-paste snippet. Options: data-position="left|right", data-color="#5b7cfa", data-label="Help", data-fullscreen="mobile|always|off" (default mobile — opens full-screen on phones so nothing looks cramped).

Support widget + unread badge

embed.jsabove needs a token per party — it suits buyer↔seller disputes you create from your backend. For plain visitor → support chat on your own site, use widget.js instead: paste one line on every page, no backend call, no token to manage.

<script src="https://znatoka.com/widget.js"
        data-site="<your widget_key>" async></script>

Your widget_key (wk_...) is public — it is safe in page source, unlike your api_key. The widget gives each visitor a random id in localStorage and keeps one open conversation per visitor, so a returning visitor lands back in the same thread. Read and reply to everything in the admin inbox.

Unread badge

When you reply and the visitor has the panel closed, a red count appears on the bubble — no setup needed, it is on by default. It works because the widget loads the chat hidden as soon as the page does, so its realtime stream is already connected before anyone clicks the bubble. The count clears the moment the visitor opens the panel, and is remembered per browser, so a page reload will not re-announce messages they already read.

If the tab is in the background, the page title also blinks (1) Pesan baru until the visitor comes back. It never blinks while they are looking at the page, and the original title is always restored. Turn it off with data-title-alert="off".

The bubble badge and the title blink are the only signals we can raise on your page. Sound and desktop notifications exist inside the chat itself, but browsers block both from a cross-origin frame, so do not rely on them reaching a visitor who has the panel closed.

Options

<script src="https://znatoka.com/widget.js"
        data-site="wk_..."
        data-label="Help"            <!-- bubble tooltip + screen-reader name -->
        data-position="left"         <!-- left | right (default right) -->
        data-color="#5b7cfa"         <!-- bubble gradient start -->
        data-fullscreen="mobile"     <!-- mobile | always | off -->
        data-title-alert="on"        <!-- on | off: blink the tab title -->
        async></script>

Everything renders in a shadow root, so your CSS and the widget's cannot affect each other. data-fullscreen="mobile" (the default) opens full-screen on phones so nothing looks cramped. The bubble is a real <button> whose accessible name carries the unread count, and the count is announced politely to screen readers.

Brand colors & chat theme

Make each chat look like your brand: colors, font and bubble shape of the chat page (/c/:token), the embed and the support widget — rendered server-side, so visitors see your colors from the first pixel. Every chat link can carry its own colors, so several brands on one account just use different parameters.

No API needed: add the colors to the chat link as URL parameters and they apply automatically, e.g. /c/<token>?primary=16a34a&mode=light&font=poppins. With embed.js or widget.js, put the same values on the script tag as data-primary, data-mode, … — they are forwarded for you. Colors are hex with or without #; an invalid value is simply ignored.

Order of precedence: Znatoka default ← account theme (/api/v1/theme) ← conversation theme ← URL parameters / data-* attributes.

SSupportonline
Hi! How can we help you today?
My order #4821 hasn't arrived yet.
Let me check that for you 🙏
12:04
Type a message…Send

1 · Chat link — just add these parameters (no API needed)

https://znatoka.com/c/<token>?mode=light&primary=e11d48&on_primary=ffffff&background=ffffff&header=fff1f2&text=1f2937&muted=6b7280&border=fecdd3&bubble_other=f3f4f6&bubble_other_text=111827&radius=18&font=poppins

2 · Embed / widget — same values as data-* attributes (also works on widget.js)

<script src="https://znatoka.com/embed.js" data-token="<token>"
        data-mode="light"
        data-primary="e11d48"
        data-on-primary="ffffff"
        data-background="ffffff"
        data-header="fff1f2"
        data-text="1f2937"
        data-muted="6b7280"
        data-border="fecdd3"
        data-bubble-other="f3f4f6"
        data-bubble-other-text="111827"
        data-radius="18"
        data-font="poppins" async></script>

3 · Optional: make it the default for every chat via API

curl -X PATCH https://znatoka.com/api/v1/theme \
  -H "X-Api-Key: <your api_key>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"light","primary":"#e11d48","on_primary":"#ffffff","background":"#ffffff","header":"#fff1f2","text":"#1f2937","muted":"#6b7280","border":"#fecdd3","bubble_other":"#f3f4f6","bubble_other_text":"#111827","radius":18,"font":"poppins"}'

Per-brand / per-conversation

POST https://znatoka.com/api/v1/conversations
X-Api-Key: <tenant api_key>
Content-Type: application/json

{
  "external_ref": "ORDER-77",
  "buyer":  { "name": "Buyer #77" },
  "seller": { "name": "Shop B" },
  "theme":  { "primary": "#16a34a", "mode": "light" }
}
# change one conversation later (only the fields you send are overridden)
PUT https://znatoka.com/api/v1/conversations/<id>/theme
{ "primary": "#0ea5e9", "bubble_other": "#e0f2fe" }

# read the current theme + the list of accepted fields
GET https://znatoka.com/api/v1/theme

# back to the default look
DELETE https://znatoka.com/api/v1/theme

# bad value -> 422
{ "error": "invalid_theme", "fields": { "primary": "must be a hex color like #1a73e8" } }

Fields

FieldTypeWhat it colors
mode"dark" | "light" | "auto"Base palette. "auto" follows the visitor’s system setting.
primaryhex colorBrand color: buttons, links, the visitor’s own bubbles, widget launcher.
on_primaryhex colorText/icon color on top of primary. Default #ffffff.
backgroundhex colorChat page background.
headerhex colorTop bar & composer background.
texthex colorMain text color.
mutedhex colorSecondary text (timestamps, hints).
borderhex colorDividers and bubble borders.
bubble_selfhex colorBubbles sent by the person viewing the chat. Default = primary.
bubble_self_texthex colorText inside bubble_self. Default = on_primary.
bubble_otherhex colorBubbles from the other side (your support team, for visitors).
bubble_other_texthex colorText inside bubble_other.
radiusnumber 0–28Bubble corner radius in px. Default 15.
font"system" | "inter" | "roboto" | "poppins" | "nunito" | "serif" | "mono"Font family.

All fields are optional. Colors must be hex (#rgb, #rrggbb, #rrggbbaa) — other CSS values are rejected so a theme can never inject styles into the page. The widget launcher uses primary unless you set data-color on the script tag.

Official SDK (Node/TypeScript)

The chatdisiniaja package wraps the REST API + HMAC webhook verification + embed helpers, fully typed.

npm install chatdisiniaja

import { Chatdisiniaja } from 'chatdisiniaja';
const cdj = new Chatdisiniaja({ apiKey: process.env.CHATDISINIAJA_API_KEY! });
const conv = await cdj.createConversation({
  externalRef: 'REFUND-123',
  buyer: { name: 'Buyer #4821' },
  seller: { name: 'Seller (anonymous)' },
});
// paste conv.buyer_embed / conv.seller_embed on each party's page

Authentication

All /api/v1/* endpoints require the header X-Api-Key: <api_key>. Participant endpoints /c/:token/* are opened directly by the buyer/seller via a secret link — no API key. Keep your api key server-side only.

Endpoints

MethodPathDescription
POST/api/v1/conversationsCreate a conversation and return buyer_url & seller_url.
GET/api/v1/conversations/:idConversation detail + full transcript.
POST/api/v1/conversations/:id/messagesSend a message as system/role. notify:false to mirror without a webhook.
POST/api/v1/conversations/:id/closeClose the conversation.
GET · PUT · PATCH · DELETE/api/v1/themeBrand colors for every chat of your account. See Brand colors.
PUT · DELETE/api/v1/conversations/:id/themeBrand colors for one conversation (overrides the account theme).
GET/c/:tokenParticipant chat UI (HTML).
GET/c/:token/infoConversation info + the other party's label.
GET/c/:token/messagesList messages.
POST/c/:token/messagesParticipant sends a message.
GET/c/:token/streamRealtime SSE.
GET/widget.jsSupport-widget loader. Public, no key — takes data-site=<widget_key>.
POST/w/:widgetKey/sessionPublic. { visitor_id } → { token }: opens or restores that visitor's conversation.

1. Create a conversation

buyer.name/seller.name is the label the other party sees — anonymize here.

POST https://znatoka.com/api/v1/conversations
X-Api-Key: <tenant api_key>
Content-Type: application/json

{
  "external_ref": "REFUND-PR05001",
  "subject": "Refund: Gmail can't log in",
  "buyer":  { "name": "Buyer #4821" },
  "seller": { "name": "Seller (anonymous)" }
}

201
{ "id": "...", "buyer_url": "https://znatoka.com/c/...", "seller_url": "https://znatoka.com/c/..." }

2. Webhook on incoming message optional

The chat works without a webhook — any party with the chat page or embed widget open receives messages in realtime over SSE. A webhook only adds server-to-server push, so you can alert a party who is not currently viewing the chat. Skipping it? Rely on the embed widget, or poll GET /api/v1/conversations/:id.

To enable it, set the tenant webhook_url in the Portal. On each incoming message we POST the event below; use open_conversation_url to notify notify_role.

POST <your tenant webhook_url>
X-Chatdisiniaja-Signature: sha256=<HMAC-SHA256(body, webhook_secret)>

{
  "event": "message.created",
  "conversation_id": "...", "external_ref": "REFUND-PR05001",
  "sender_role": "buyer",
  "preview": "Hi, the account won't log in...",
  "notify_role": "seller",
  "open_conversation_url": "https://znatoka.com/c/<token>"
}

3. Verify the webhook signature (Node.js)

Only needed if you use webhooks. Compute the HMAC over the raw request body, before JSON parsing.

const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const sig = (header || '').replace(/^sha256=/, '');
  const exp = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return sig.length === exp.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(exp));
}