Endpoint voor klantgegevens: bestellingen, verzending en facturen
Technische referentie voor de koppeling van klantgegevens: het ondertekende verzoek van reactive.chat aan uw webwinkel, antwoordschema, limieten, foutredenen, complete referentie-endpoints in PHP en Node en import via de API.
Met klantgegevens beantwoordt de AI-assistent vragen zoals ‘Waar is mijn pakket?’ of ‘Is mijn factuur betaald?’, alleen voor bevestigde bezoekers en alleen over hun eigen bestellingen. Deze pagina is de technische referentie voor degene die het endpoint bouwt. Wat de assistent toont en welke velden u in uw account vrijgeeft, staat in Klantgegevens in de chat.
Beschikbaar in de betaalde abonnementen (ook tijdens de proefperiode).
Vereisten
- Identiteitsverificatie met exp. De bezoeker moet zijn ingelogd met een geldige
userHashenexp(Bezoekers identificeren). Zonderexpgeldt de identiteit als bevestigd, maar niet als actueel — dan wordt niets opgehaald. - Instelling in uw account onder Project → Klantgegevens: modus kiezen, endpoint invullen, velden vrijgeven, melding instellen voor bezoekers die niet zijn ingelogd.
Modi:
- Ophalen – reactive.chat bevraagt uw endpoint bij elke passende vraag. Het antwoord wordt niet opgeslagen. Aanbevolen.
- Import – uw webwinkel stuurt de bestellingen van een klant via de API naar ons; ze worden bij ons versleuteld opgeslagen, maximaal 90 dagen.
- Beide – eerst de geïmporteerde gegevens; als daar niets wordt gevonden, wordt opgehaald.
- Uit.
Het verzoek
reactive.chat roept uw endpoint met POST aan:
POST /rc-kundendaten HTTP/1.1
Host: shop.your-company.com
Content-Type: application/json
Accept: application/json
User-Agent: reactive.chat-kundendaten/1
X-RC-Zeit: 1792503600
X-RC-Signatur: 5d41402abc4b2a76b9719d911017c592...
{"user_id":"4711","anliegen":"lieferung","referenz":"2026-0815","sprache":"en","nonce":"9f86d081884c7d659a2feaa0c55ad015"}
Headers:
- X-RC-Zeit – Unix-seconden op het tijdstip van het verzoek.
- X-RC-Signatur –
hex(HMAC_SHA256(endpoint_secret, X-RC-Zeit + "." + raw_body)), 64 kleine tekens, zonder voorvoegsel. De sleutel is het endpointgeheim uit uw account als tekenreeks (het begint metrckd_).
Body:
- user_id (tekenreeks) – de bevestigde ID uit
identify. Nooit iets uit de chat. - anliegen (tekenreeks) –
bestellung(bestelling),lieferung(levering),rechnung(factuur),retoure(retour);testvoor de testknop. - referenz (tekenreeks of
null) – een nummer dat de klant in de chat heeft genoemd. Alleen een filter binnen de bestellingen van dezeuser_id— het nummer van iemand anders mag niets opleveren. - sprache (tekenreeks) – twee letters, bijvoorbeeld
de. - nonce (tekenreeks) – 32 hexadecimale tekens, nieuw voor elk verzoek.
Uw endpoint moet:
- de handtekening over de ruwe body controleren (in constante tijd, bijvoorbeeld
hash_equals), niet over opnieuw gecodeerde JSON; - verzoeken weigeren waarvan
X-RC-Zeitmeer dan 300 seconden van uw klok afwijkt; - elke
nonceslechts eenmaal accepteren (aanbevolen); - alleen gegevens van de opgegeven
user_idteruggeven; - via
httpsop poort 443 op een openbaar adres bereikbaar zijn, zonder omleidingen en zonder gebruikersnaam/wachtwoord in het adres; - binnen 3 seconden volledig antwoorden (verbinding opzetten maximaal 2 seconden).
Het antwoord
Precies HTTP 200 met een JSON-object. Geen treffer is {"bestellungen":[]}, ook met 200.
{
"bestellungen": [
{
"nummer": "2026-0815",
"datum": "2026-09-28",
"status": "shipped",
"summe": "49.90",
"waehrung": "EUR",
"positionen": [ { "name": "Balance bike blue", "menge": 1 } ],
"versand": { "dienst": "DHL", "sendungsnummer": "00340434161234567890", "status": "out for delivery" },
"rechnung": { "nummer": "R-2026-1201", "status": "paid", "faellig_am": "2026-10-12" }
}
]
}
Velden per bestelling:
- nummer (tekenreeks, verplicht) – een bestelling zonder nummer wordt verworpen.
- datum (tekenreeks,
YYYY-MM-DD). - status (tekenreeks) – bestelstatus als gewone tekst.
- summe (tekenreeks of getal), waehrung (ISO 4217, bijvoorbeeld
EUR). - positionen (lijst, maximaal 20) – elk met
nameenmenge. - versand (object) –
dienst,sendungsnummer,status, optioneelortenplz(alleen als dit uitdrukkelijk in uw account is vrijgegeven). - rechnung (object) –
nummer,status,faellig_am.
Limieten: maximaal 10 bestellingen, 20 artikelen per bestelling, teksten van maximaal 200 tekens, het hele antwoord maximaal 16.384 bytes. Onbekende velden en velden die niet in uw account zijn vrijgegeven, worden zonder foutmelding verworpen.
Nooit overgenomen, ook niet als u ze verstuurt: straat en verdere adresregels, namen, e-mail, telefoon, IBAN/BIC/rekening- en kaartgegevens, geboortedatum, wachtwoorden/tokens/sleutels, belasting- en identiteitsnummers. Verstuur ze bij voorkeur helemaal niet.
Wat er met het antwoord gebeurt: het wordt alleen gebruikt voor dat ene antwoord van de assistent en niet opgeslagen, noch in het gesprek, noch in de kennisbank; het logboek registreert alleen aantal en duur. Als uw endpoint niet op tijd of met een fout antwoordt, reageert de assistent alsof er geen gegevens zijn.
Referentie-endpoint in PHP
<?php
declare(strict_types=1);
$GEHEIMNIS = (string) getenv('RC_KD_GEHEIMNIS'); // rckd_... from the account
function antworte(int $status, array $daten): never {
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
echo json_encode($daten, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') { antworte(405, ['fehler' => 'POST only']); }
$body = (string) file_get_contents('php://input', false, null, 0, 4097);
if (strlen($body) > 4096) { antworte(413, ['fehler' => 'too large']); }
$zeit = (string) ($_SERVER['HTTP_X_RC_ZEIT'] ?? '');
$sig = strtolower((string) ($_SERVER['HTTP_X_RC_SIGNATUR'] ?? ''));
if (!preg_match('/^\d{9,11}$/', $zeit) || abs(time() - (int) $zeit) > 300) {
antworte(401, ['fehler' => 'zeit']);
}
if (!hash_equals(hash_hmac('sha256', $zeit . '.' . $body, $GEHEIMNIS), $sig)) {
antworte(401, ['fehler' => 'signatur']);
}
$j = json_decode($body, true);
$userId = is_string($j['user_id'] ?? null) ? $j['user_id'] : '';
$referenz = is_string($j['referenz'] ?? null) ? trim($j['referenz']) : '';
$nonce = is_string($j['nonce'] ?? null) ? $j['nonce'] : '';
if ($userId === '' || !preg_match('/^[0-9a-f]{32}$/', $nonce)) {
antworte(400, ['fehler' => 'anfrage']);
}
$pdo = new PDO(getenv('SHOP_DB_DSN'), getenv('SHOP_DB_USER'), getenv('SHOP_DB_PASS'),
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
// nonce only once: CREATE TABLE rc_nonce (nonce CHAR(32) PRIMARY KEY, zeit INT NOT NULL);
$pdo->prepare('DELETE FROM rc_nonce WHERE zeit < ?')->execute([time() - 600]);
try {
$pdo->prepare('INSERT INTO rc_nonce (nonce, zeit) VALUES (?, ?)')->execute([$nonce, time()]);
} catch (PDOException $e) {
antworte(409, ['fehler' => 'nonce already used']);
}
if ($userId === 'rc-test') { antworte(200, ['bestellungen' => []]); } // test button
// ONLY this customer; the reference is only a filter
$sql = 'SELECT id, nummer, datum, status, summe, waehrung, versand_dienst, sendungsnummer,
versand_status, rechnung_nummer, rechnung_status, faellig_am
FROM bestellungen WHERE kunde_id = :u';
$w = [':u' => $userId];
if ($referenz !== '') {
$sql .= ' AND (nummer = :r1 OR rechnung_nummer = :r2 OR sendungsnummer = :r3)';
$w += [':r1' => $referenz, ':r2' => $referenz, ':r3' => $referenz];
}
$st = $pdo->prepare($sql . ' ORDER BY datum DESC LIMIT 10');
$st->execute($w);
$pos = $pdo->prepare('SELECT name, menge FROM bestell_positionen WHERE bestellung_id = ? LIMIT 20');
$aus = [];
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $b) {
$pos->execute([$b['id']]);
$aus[] = [
'nummer' => (string) $b['nummer'],
'datum' => $b['datum'],
'status' => $b['status'],
'summe' => $b['summe'],
'waehrung' => $b['waehrung'],
'positionen' => $pos->fetchAll(PDO::FETCH_ASSOC),
'versand' => ['dienst' => $b['versand_dienst'], 'sendungsnummer' => $b['sendungsnummer'],
'status' => $b['versand_status']],
'rechnung' => ['nummer' => $b['rechnung_nummer'], 'status' => $b['rechnung_status'],
'faellig_am' => $b['faellig_am']],
];
}
antworte(200, ['bestellungen' => $aus]);
Referentie-endpoint in Node.js (Express)
const crypto = require('crypto');
const express = require('express');
const app = express();
const GEHEIM = process.env.RC_KD_GEHEIMNIS;
const gesehen = new Map(); // nonce -> time; a database in production
app.post('/rc-kundendaten', express.raw({ type: 'application/json', limit: '4kb' }), async (req, res) => {
const body = req.body.toString('utf8'); // RAW body
const zeit = String(req.get('X-RC-Zeit') || '');
const sig = String(req.get('X-RC-Signatur') || '').toLowerCase();
if (!/^\d{9,11}$/.test(zeit) || Math.abs(Date.now() / 1000 - Number(zeit)) > 300) {
return res.status(401).json({ fehler: 'zeit' });
}
const soll = crypto.createHmac('sha256', GEHEIM).update(zeit + '.' + body).digest('hex');
if (sig.length !== soll.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(soll))) {
return res.status(401).json({ fehler: 'signatur' });
}
const j = JSON.parse(body);
if (!/^[0-9a-f]{32}$/.test(j.nonce || '') || gesehen.has(j.nonce)) {
return res.status(409).json({ fehler: 'nonce' });
}
gesehen.set(j.nonce, Date.now());
if (j.user_id === 'rc-test') { return res.json({ bestellungen: [] }); }
const bestellungen = await ladeBestellungen(j.user_id, j.referenz); // ONLY this customer
res.json({ bestellungen: bestellungen.slice(0, 10) });
});
De verbinding testen
Verbinding testen in uw account roept uw endpoint aan met user_id rc-test en anliegen test. Geef hiervoor een lege lijst of een voorbeeldbestelling terug. Als de aanroep mislukt, noemt uw account een van deze redenen:
- kein_endpunkt, kein_geheimnis – er ontbreekt iets in uw account.
- url_ungueltig, nur_https, port_verboten, zugangsdaten_in_url – het adres is niet toegestaan (alleen
https, poort 443, geen toegangsgegevens in het adres). - host_unaufloesbar, adresse_privat, adresse_gewechselt – de naam kan niet worden omgezet of wordt omgezet naar een privéadres.
- tls, verbindung, zeitueberschreitung – certificaat, verbinding of de termijn van 3 seconden.
- umleitung – uw endpoint antwoordt met 3xx.
- http_<code> – een andere status dan 200 (bijvoorbeeld
http_401: uw handtekeningcontrole weigert het verzoek). - antwort_zu_gross, antwort_kein_json, antwort_ohne_bestellungen – het antwoord voldoet niet aan het schema.
- tarif – het project heeft geen betaald abonnement.
Import via API
Als uw webwinkel geen endpoint kan aanbieden, stuurt u de gegevens naar ons. U hebt een API-sleutel nodig met de scope alleen import van klantgegevens (kundendaten:schreiben) en de modus Import of Beide.
curl -X PUT "https://reactive.chat/v1/kundendaten/4711?project_id=123" \
-H "Authorization: Bearer rc_live_..." \
-H "Content-Type: application/json" \
-d '{"bestellungen":[{"nummer":"2026-0815","datum":"2026-09-28","status":"shipped"}]}'
- PUT /v1/kundendaten/{user_id} – vervangt alle bestellingen van deze klant (maximaal 50; een lege lijst verwijdert ze). Antwoord
{"data":{"stored":1,"dropped":0,"max_orders":50,"expires_in_days":90}}. - DELETE /v1/kundendaten/{user_id} – verwijdert ze; werkt altijd, ook zonder abonnement. Antwoord
{"data":{"deleted":1}}. - project_id – verplicht zodra uw werkruimte meer dan één project heeft.
- user_id – dezelfde ID die u in
identifyondertekent; URL-gecodeerd, maximaal 200 tekens. Een gecodeerde schuine streep (%2F) is niet mogelijk. - Er is geen
GET.
Geïmporteerde gegevens worden versleuteld opgeslagen (het klantnummer alleen als hash) en 90 dagen na de laatste import verwijderd. Verstuur wijzigingen daarom doorlopend, bijvoorbeeld bij elke statuswijziging.
Fouten: 402 plan_required (geen betaald abonnement), 409 import_disabled (de modus staat import niet toe, met kd_modus), 400 bad_request met param (user_id, project_id, bestellungen), 404 (project van iemand anders). Het algemene foutformaat staat onder Server-API.
Bezoekers die niet zijn ingelogd
Als iemand zonder bevestigde identiteit naar een bestelling vraagt, haalt de assistent niets op; in plaats daarvan wordt uw melding uit het account getoond (tekst per taal, optioneel een inloglink) of overgedragen aan uw team.
Volgende: Webhooks · Server-API
अंतिम अपडेट: