Entwickler

API

Mit der API verwenden Sie Ihre Bots aus Ihrer eigenen Software, etwa einem Intranet, einer App oder einem CRM. Der Bot antwortet genau wie im Chat auf Ihrer Website: mit Ihren eigenen Inhalten, Standorten, Öffnungszeiten, Regeln und Auswahlabläufen.

Authentifizierung

Erstellen Sie einen Schlüssel im Portal unter Verbindungen → API-Schlüssel. Geben Sie ihm einen Namen und wählen Sie, ob er Zugriff auf alle Bots Ihres Kontos (auch Bots, die Sie später hinzufügen) oder auf einen oder mehrere Bots hat und ob er Bots erstellen darf. Sie sehen den Schlüssel nur ein einziges Mal. Sie können beliebig viele erstellen und jeden einzeln widerrufen.

Senden Sie den Schlüssel im Header jeder Anfrage mit:

Authorization: Bearer cop_…
Ein API-Schlüssel gehört auf Ihren Server. Verwenden Sie ihn niemals in Code, der in einem Browser oder einer App bei Ihren Besuchern landet. Für Ihre Website verwenden Sie das normale Widget.

Schlüssel prüfen

GET /key

Gibt den Namen des Schlüssels, das Konto und die Bots zurück, auf die er zugreifen kann. Praktisch, um zu testen, ob alles funktioniert.

{
  "name": "Intranet",
  "account": "Kindercentrum",
  "all_bots": false,
  "bots": [{ "id": 24, "name": "Kindercentrum" }]
}

Bots abrufen

GET /bots

Alle Bots, auf die dieser Schlüssel Zugriff hat. Pausierte Bots sind nicht aufgeführt. Mit der id wählen Sie den Bot in den folgenden Calls.

{
  "data": [
    {
      "id": 24,
      "name": "Kindercentrum",
      "site_url": "https://www.kindercentrum.be",
      "status": "active",
      "default_locale": "nl",
      "locales": ["nl", "fr"]
    }
  ]
}

Ein Bot

GET /bots/{id}

Dieselben Daten, plus die Begrüßung, die Start-Schaltflächen und die Standorte des Bots.

{
  "data": {
    "id": 24,
    "name": "Kindercentrum",
    …
    "greeting": "Hallo! Ik ben de assistent van Kindercentrum. Waarmee kan ik u helpen?",
    "start_choices": ["Inschrijven", "Openingsuren"],
    "offices": [{ "id": 7, "name": "Biezebos", "city": "Gent" }],
    "progress": { "percent": 100, "status": "Klaar", "ready": true, "pages": 84 }
  }
}

Bot erstellen

POST /bots

Erstellt einen neuen Bot für eine Website, wie Neuer Bot im Portal: Die Website wird sofort gelesen, Farben und Symbol werden übernommen, und wer den Schlüssel erstellt hat, erhält eine E-Mail, sobald der Bot zum Testen bereit ist. Verfolgen Sie den Fortschritt mit GET /bots/{id} (progress).

Nur mit einem Schlüssel, bei dem Darf Bots erstellen aktiviert ist. Jeder Bot hat einen eigenen Tarif: Kostet der neue Bot extra, erhalten Sie zuerst 402 mit dem Preis und senden die Anfrage erneut mit "confirm_price": true. Höchstens 10 neue Bots pro Stunde und Schlüssel.

FeldTypBedeutung
nameTextName des Bots, max. 100 Zeichen. Pflichtfeld.
site_urlTextWebsite, die der Bot liest, zum Beispiel www.ihrefirma.de. Pflichtfeld.
default_localeTextHauptsprache: nl, en, fr, de oder es. Standardmäßig die Sprache Ihres Kontos.
localesListeZusätzliche Sprachen des Bots. Sprachen, die der Bot auf der Website findet, kommen automatisch hinzu.
planTextlite, plus, pro oder business. Ohne Tarif wählen wir den kleinsten Tarif, der zur Website passt.
support_emailTextAdresse für Fragen, die der Bot weiterleitet; standardmäßig die Support-Adresse Ihres Kontos.
auto_approveja/neinNeue und geänderte Seiten sofort verwenden (standardmäßig ja) oder zuerst im Portal freigeben lassen.
for_customerja/neinNur für Partner: ein Bot für einen Kunden, der nicht unter Ihr eigenes Abonnement fällt.
confirm_priceja/neinBestätigt den Preis, wenn der Bot extra kostet.
POST /bots
{ "name": "Webshop", "site_url": "www.webshop.be", "plan": "pro", "confirm_price": true }

201 Created
{
  "data": {
    "id": 31,
    "name": "Webshop",
    "site_url": "https://www.webshop.be/",
    "status": "active",
    …
    "progress": { "percent": 10, "status": "Website wordt gelezen", "ready": false, "pages": 0 },
    "embed_code": "<script src="https://chatonpage.com/widget.js" data-key="pk_…" async></script>",
    "portal_url": "https://chatonpage.com/app/…/bots/31/edit"
  }
}

Frage stellen

POST /bots/{id}/messages

Sendet eine Nachricht an den Bot und gibt die Antwort zurück. Ohne conversation beginnt ein neues Gespräch. Senden Sie die conversation aus der Antwort bei der nächsten Frage mit, dann merkt sich der Bot, worum es im Gespräch geht.

FeldTypBedeutung
messageTextDie Frage oder die Antwort des Benutzers, max. 4.000 Zeichen.
conversation optionalTextID eines Gesprächs, das mit diesem Schlüssel gestartet wurde.
locale optionalTextSprache eines neuen Gesprächs (nl, fr, en, …). Standardmäßig die Sprache des Bots. Antwortet der Benutzer in einer anderen Sprache, folgt der Bot dieser.
office_id optionalZahlStandort, um den es in einem neuen Gespräch geht.
user optionalTextIhre eigene Kennung für den Benutzer, max. 40 Zeichen. So finden Sie seine Gespräche im Portal wieder. Keine Namen oder E-Mail-Adressen.
stream optionalbooleantrue, um die Antwort Wort für Wort zu erhalten. Siehe Streaming.
{
  "conversation": "9f0c2a4e-6b1d-4c3e-9a57-2f1e8d7c6b5a",
  "reply": {
    "message_id": 18342,
    "content": "Voor welk type opvang wil je inschrijven?",
    "sources": [{ "title": "Inschrijven", "url": "https://www.kindercentrum.be/inschrijven" }],
    "choices": ["Kinderdagverblijf", "Buitenschoolse opvang (BKO)", "Gezinsopvang"],
    "office_options": [],
    "escalated": false
  }
}

Streaming

Mit "stream": true erhalten Sie einen text/event-stream (Server-Sent Events), sodass Sie die Antwort schon anzeigen, während der Bot schreibt:

event: start
data: {"conversation":"9f0c2a4e-…"}

event: delta
data: {"text":"Voor welk type "}

event: delta
data: {"text":"opvang wil je inschrijven?"}

event: done
data: {"conversation":"9f0c2a4e-…","reply":{ …zoals hierboven… }}

Geht unterwegs etwas schief, folgt event: error mit einer message.

Gespräch abrufen

GET /bots/{id}/conversations/{conversation}

Alle Nachrichten eines Gesprächs, das mit diesem Schlüssel gestartet wurde.

{
  "data": {
    "id": "9f0c2a4e-…",
    "bot_id": 24,
    "locale": "nl",
    "office_id": null,
    "started_at": "2026-10-17T10:12:03+02:00",
    "messages": [
      { "id": 18341, "role": "user", "content": "Ik wil mijn kind inschrijven", "choices": [], "at": "…" },
      { "id": 18342, "role": "assistant", "content": "Voor welk type opvang …", "choices": ["Kinderdagverblijf", …], "at": "…" }
    ]
  }
}

Leads weiterleiten

Fragt ein Besucher danach, kontaktiert zu werden, dann erstellt der Bot einen Lead mit Name, E-Mail-Adresse, Telefonnummer, Frage und Zusammenfassung. Mit Webhooks wird jeder neue Lead sofort an Ihr CRM, Zapier, Make oder Ihre eigene Software gesendet. Sie können Leads und Events auch selbst abrufen.

Leads enthalten Kontaktdaten von Besuchern. Daher funktionieren die folgenden Aufrufe nur mit einem Schlüssel, bei dem Zugriff auf Leads aktiviert ist, und nur für die Bots dieses Schlüssels. Ohne diesen Zugriff erhalten Sie 403.

Webhooks

Erstellen Sie einen Webhook im Portal unter Verbindungen → Webhooks oder über die API. Wählen Sie die URL, die Events und die Bots aus. Bei jedem Event senden wir einen POST mit JSON an diese URL.

EventWann
lead.createdEin Besucher hat um Kontaktaufnahme gebeten.
lead.updatedDie Nachverfolgung eines Leads hat sich geändert (new, contacted, closed), im Portal oder über die API. Mit previous_status.
report.createdDer Bot hat eine Beschwerde oder Fehlermeldung erfasst. Fünf Minuten später, damit Ergänzungen des Besuchers enthalten sind.
POST https://jouw-crm.example.com/hooks/chatonpage
Content-Type: application/json
X-ChatOnPage-Event: lead.created
X-ChatOnPage-Event-Id: 5821
X-ChatOnPage-Delivery: 9310
X-ChatOnPage-Signature: t=1792923600,v1=5f2b…

{
  "id": 5821,
  "type": "lead.created",
  "created_at": "2026-10-25T14:02:11+02:00",
  "account": { "id": 3, "name": "Kindercentrum" },
  "bot": { "id": 24, "name": "Kindercentrum" },
  "data": {
    "lead": {
      "id": 1288,
      "status": "new",
      "name": "An Peeters",
      "email": "an@example.com",
      "phone": "+32 470 12 34 56",
      "question": "Is er nog plaats in de BKO vanaf januari?",
      "summary": "Ouder zoekt opvang voor twee kinderen vanaf januari.",
      "language": "nl",
      "office": { "id": 7, "name": "Biezebos" },
      "conversation_id": "9f0c2a4e-6b1d-4c3e-9a57-2f1e8d7c6b5a",
      "portal_url": "https://chatonpage.com/app/kindercentrum/leads",
      "created_at": "2026-10-25T14:02:09+02:00",
      "updated_at": "2026-10-25T14:02:09+02:00"
    }
  }
}

Signatur prüfen

Jeder Webhook hat ein Geheimnis (whsec_…), im Portal unter Geheimnis einsehbar. Der Header X-ChatOnPage-Signature enthält t (Zeitstempel, Unix-Sekunden) und v1: den HMAC-SHA256 von t, einem Punkt und dem Roh-Body, mit dem Geheimnis als Schlüssel. Vergleichen Sie diese und lehnen Sie Nachrichten ab, bei denen t mehr als 5 Minuten abweicht.

// Node.js (Express): app.post('/hooks/chatonpage', express.raw({ type: 'application/json' }), …)
const crypto = require('crypto');
const { t, v1 } = Object.fromEntries(req.get('X-ChatOnPage-Signature').split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', process.env.CHATONPAGE_WEBHOOK_SECRET).update(`${t}.${req.body}`).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) && Math.abs(Date.now() / 1000 - t) < 300;
# PHP
[$t, $v1] = sscanf($_SERVER['HTTP_X_CHATONPAGE_SIGNATURE'], 't=%d,v1=%s');
$body = file_get_contents('php://input');
$valid = hash_equals(hash_hmac('sha256', $t.'.'.$body, getenv('CHATONPAGE_WEBHOOK_SECRET')), $v1) && abs(time() - $t) < 300;

Events abrufen

GET /events

Dieselbe Events wie bei Webhooks, beginnend mit den ältesten, für Nutzer, die lieber selbst abrufen (z. B. alle 5 Minuten). Speichere next_after und sende es als after mit: Dann erhältst du nur die neuen Events. Events werden 30 Tage lang gespeichert.

ParameterBedeutung
after optionalNur Events mit einer höheren ID.
type optionalEin oder mehrere Events, durch Kommas getrennt, z. B. lead.created.
bot optionalNur Events von diesem Bot.
limit optional1 bis 100, Standard 50.
GET /events?type=lead.created&after=5820

{
  "data": [{ "id": 5821, "type": "lead.created", "created_at": "…", "account": { … }, "bot": { … }, "data": { "lead": { … } } }],
  "next_after": 5821
}

Leads

GET /leads

Leads der Bots des Schlüssels, neueste zuerst. Filter mit status (new, contacted, closed), bot und since (Datum oder Zeitstempel, ISO 8601). Gibt es eine nächste Seite, senden Sie next_before als before mit.

GET /leads/{id}

Ein Lead mit denselben Feldern wie bei lead.created, zusätzlich bot.

PATCH /leads/{id}

Passt die Nachverfolgung an, z. B. {"status": "contacted"}, sobald der Lead in Ihrem CRM ist. Das sehen Sie auch im Portal, und es löst ein Event lead.updated aus.

Webhooks über die API

So verbinden Automatisierungsplattformen wie Zapier und Make sich an und wieder ab. Ein Schlüssel sieht nur die Webhooks, die er selbst erstellt hat. Wird der Schlüssel widerrufen, verschwinden auch seine Webhooks.

POST /webhooks
FeldTypBedeutung
urlTextÖffentliche HTTPS-Adresse. Pflichtfeld.
events optionalListeStandardmäßig alle Events.
bots optionalListeIDs der Bots. Standardmäßig die Bots des Schlüssels.
name optionalTextName im Portal. Standardmäßig der Name des Schlüssels.
POST /webhooks
{ "url": "https://hooks.zapier.com/hooks/standard/123/abc", "events": ["lead.created"] }

201 Created
{
  "data": {
    "id": 12,
    "name": "Zapier",
    "url": "https://hooks.zapier.com/hooks/standard/123/abc",
    "events": ["lead.created"],
    "bots": "all",
    "active": true,
    "disabled_reason": null,
    "created_at": "…",
    "secret": "whsec_…"
  }
}

Das Geheimnis steht nur in dieser Antwort. Außerdem: GET /webhooks (Liste), DELETE /webhooks/{id} (abmelden) und POST /webhooks/{id}/test (Testereignis). Höchstens 25 Webhooks pro Konto.

Auswahl

Bei einem Auswahlablauf (zum Beispiel Anmeldung) gibt der Bot choices mit. Zeigen Sie diese als Schaltflächen an und senden Sie den gewählten Text wörtlich als nächste message zurück, mit derselben conversation. Der Benutzer darf auch etwas anderes eingeben: Der Bot erkennt dann selbst, ob es eine Auswahl oder eine neue Frage ist.

Fehler und Limits

Bei einem Fehler erhalten Sie einen HTTP-Status mit {"error": "…"}. Bei ungültigen Feldern (422) stehen die Details in errors.

StatusBedeutung
401Kein, ungültiger oder widerrufener Schlüssel.
402Das Konto ist nicht aktiv (zum Beispiel Testphase abgelaufen) oder bei POST /bots: Der Preis muss bestätigt werden ("code": "price_confirmation_required").
403Dieser Schlüssel darf keine Bots erstellen oder hat keinen Zugriff auf Leads.
404Der Bot, das Gespräch, der Lead oder der Webhook existiert nicht, oder dieser Schlüssel hat keinen Zugriff darauf.
422Ein Feld fehlt oder ist ungültig.
429Zu viele Fragen: maximal 60 pro Minute pro Schlüssel, plus das Tageslimit pro Bot.

Jede Antwort zählt zu den Antworten in Ihrem Abonnement, genau wie im Chat auf Ihrer Website. Gespräche über die API finden Sie im Portal unter Gespräche.

Beispiele

curl

curl https://chatonpage.com/api/v1/bots \
  -H "Authorization: Bearer $CHATONPAGE_KEY"

curl https://chatonpage.com/api/v1/bots/24/messages \
  -H "Authorization: Bearer $CHATONPAGE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Wat zijn de openingsuren in Gent?"}'

JavaScript (Node.js 18+)

const res = await fetch('https://chatonpage.com/api/v1/bots/24/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATONPAGE_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ message: 'Wat zijn de openingsuren in Gent?', conversation }),
});
const { conversation: id, reply } = await res.json();
console.log(reply.content);

Python

import os, requests

r = requests.post(
    "https://chatonpage.com/api/v1/bots/24/messages",
    headers={"Authorization": f"Bearer {os.environ['CHATONPAGE_KEY']}"},
    json={"message": "Wat zijn de openingsuren in Gent?"},
    timeout=60,
)
data = r.json()
print(data["reply"]["content"])
conversation = data["conversation"]  # meesturen bij de volgende vraag