Дані клієнтів: замовлення, доставка й рахунки через API
Технічний довідник підключення даних клієнтів: підписаний запит reactive.chat до магазину, схема відповіді, ліміти, причини збоїв, повні кінцеві точки PHP і Node та імпорт API.
З даними клієнтів ШІ-асистент відповідає на запитання «Де моя посилка?» або «Чи оплачено мій рахунок?» — лише для підтверджених відвідувачів і лише про власні замовлення. Ця сторінка — технічний довідник для розробника кінцевої точки. Показ асистента й вибір полів в обліковому записі описано в Даних клієнтів у чаті.
Доступно на платних тарифах (також під час пробного періоду).
Вимоги
- Підтвердження особи з exp. Відвідувач має ввійти з дійсним
userHashіexp(Ідентифікація відвідувачів). Безexpвін підтверджений, але не актуальний — тоді дані не запитуються. - Налаштування облікового запису в Проєкт → Дані клієнтів: вибрати режим, ввести кінцеву точку, ввімкнути поля, задати повідомлення для тих, хто не ввійшов.
Режими:
- Запит — reactive.chat звертається до кінцевої точки для кожного відповідного запитання. Відповідь не зберігається. Рекомендовано.
- Імпорт — магазин надсилає замовлення клієнта нам через API; у нас вони зашифровані, до 90 днів.
- Обидва — спочатку імпорт, якщо нічого не знайдено — запит.
- Вимкнено.
Запит
reactive.chat викликає кінцеву точку через POST:
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"}
Заголовки:
- X-RC-Zeit — секунди Unix на момент запиту.
- X-RC-Signatur —
hex(HMAC_SHA256(endpoint_secret, X-RC-Zeit + "." + raw_body)), 64 малі символи, без префікса. Ключ — секрет кінцевої точки з облікового запису як рядок (починається зrckd_).
Тіло:
- user_id (рядок) — підтверджений ID із
identify. Ніколи дані з чату. - anliegen (рядок) —
bestellung(замовлення),lieferung(доставка),rechnung(рахунок),retoure(повернення);testдля кнопки тесту. - referenz (рядок або
null) — номер із чату клієнта. Лише фільтр у замовленнях цьогоuser_id: чужий номер нічого не має знаходити. - sprache (рядок) — дві літери, наприклад
de. - nonce (рядок) — 32 шістнадцяткові символи, нові на кожен запит.
Кінцева точка має:
- перевіряти підпис за сирим тілом (за сталий час, наприклад
hash_equals), не за повторно закодованим JSON; - відхиляти запити, чий
X-RC-Zeitвідрізняється від вашого годинника більш як на 300 секунд; - приймати кожен
nonceлише один раз (рекомендовано); - повертати лише дані заданого
user_id; - бути доступною через
httpsна порту 443 за публічною адресою, без перенаправлень та імені користувача/пароля в адресі; - повністю відповідати за 3 секунди (підключення до 2 секунд).
Відповідь
Рівно HTTP 200 з об’єктом JSON. Немає збігу — {"bestellungen":[]}, теж із 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" }
}
]
}
Поля замовлення:
- nummer (рядок, обов’язковий) — без номера замовлення відкидається.
- datum (рядок,
YYYY-MM-DD). - status (рядок) — стан замовлення звичайним текстом.
- summe (рядок або число), waehrung (ISO 4217, наприклад
EUR). - positionen (список, до 20) — кожна з
nameйmenge. - versand (об’єкт) —
dienst,sendungsnummer,status, необов’язковіortіplz(лише за явного ввімкнення в обліковому записі). - rechnung (об’єкт) —
nummer,status,faellig_am.
Ліміти: до 10 замовлень, 20 позицій на замовлення, тексти до 200 символів, уся відповідь до 16 384 байтів. Невідомі й неввімкнені поля відкидаються без помилки.
Ніколи не приймаються, навіть якщо надіслати: вулиця й інші рядки адреси, імена, пошта, телефон, IBAN/BIC/рахунок і карткові дані, дата народження, паролі/токени/ключі, податкові номери й номери документів. Краще взагалі не надсилати їх.
Що відбувається з відповіддю: використовується лише для однієї відповіді асистента й не зберігається ані в розмові, ані в базі знань; журнал фіксує лише кількість і тривалість. Якщо кінцева точка не відповіла вчасно або повернула помилку, асистент відповідає як за відсутності даних.
Еталонна кінцева точка 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]);
Еталонна кінцева точка 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) });
});
Перевірка з’єднання
Перевірити з’єднання викликає кінцеву точку з user_id rc-test та anliegen test. Поверніть порожній список чи зразок замовлення. За невдачі обліковий запис називає причину:
- kein_endpunkt, kein_geheimnis — чогось бракує в обліковому записі.
- url_ungueltig, nur_https, port_verboten, zugangsdaten_in_url — адресу не дозволено (лише
https, порт 443, без облікових даних в адресі). - host_unaufloesbar, adresse_privat, adresse_gewechselt — ім’я не визначається або вказує на приватну адресу.
- tls, verbindung, zeitueberschreitung — сертифікат, з’єднання або строк 3 секунди.
- umleitung — кінцева точка відповідає 3xx.
- http_<code> — стан, відмінний від 200 (наприклад
http_401: перевірка підпису відхиляє запит). - antwort_zu_gross, antwort_kein_json, antwort_ohne_bestellungen — відповідь не відповідає схемі.
- tarif — проєкт не на платному тарифі.
Імпорт через API
Якщо магазин не надає кінцевої точки, надсилайте дані нам. Потрібен ключ API з правом лише імпорт даних клієнтів (kundendaten:schreiben) і режим Імпорт або Обидва.
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} — замінює всі замовлення клієнта (до 50; порожній список видаляє). Відповідь
{"data":{"stored":1,"dropped":0,"max_orders":50,"expires_in_days":90}}. - DELETE /v1/kundendaten/{user_id} — видаляє; працює завжди, навіть без тарифу. Відповідь
{"data":{"deleted":1}}. - project_id — обов’язковий, щойно в робочому просторі понад один проєкт.
- user_id — той самий ID, який ви підписуєте в
identify; URL-кодований, до 200 символів. Кодована скісна риска (%2F) неможлива. - Немає
GET.
Імпортовані дані зберігаються зашифрованими (номер клієнта лише як хеш) та видаляються через 90 днів після останнього імпорту. Тому постійно надсилайте зміни, наприклад за кожної зміни стану.
Помилки: 402 plan_required (немає платного тарифу), 409 import_disabled (режим не дозволяє імпорт, з kd_modus), 400 bad_request із param (user_id, project_id, bestellungen), 404 (чужий проєкт). Загальний формат — Серверний API.
Відвідувачі, які не ввійшли
Якщо людина без підтвердженої особи запитує про замовлення, асистент не отримує даних; натомість показує ваше повідомлення з облікового запису (за мовами, необов’язково з посиланням входу) або передає команді.
Далі: Вебхуки · Серверний API
Оновлено: