كيف يمكننا المساعدة؟

يرجى توضيح الموضوع بإيجاز، وسنرد بالبريد الإلكتروني.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
إلى الصفحة الرئيسية

Endpoint zákaznických dat: objednávky, doprava a faktury

Technická reference propojení zákaznických dat – podepsaný požadavek z reactive.chat do vašeho e-shopu, schéma odpovědi, limity, důvody selhání, úplný referenční endpoint v PHP a Node a import přes API.

Se zákaznickými daty asistent AI odpovídá na otázky jako „Kde je můj balík?“ nebo „Je moje faktura zaplacená?“ – pouze potvrzeným návštěvníkům a pouze o jejich vlastních objednávkách. Tato stránka je technickou referencí pro osobu, která vytváří endpoint. Co asistent zobrazuje a která pole povolíte v účtu, popisují Zákaznická data v chatu.

Dostupné v placených tarifech (i během zkušebního období).

Předpoklady

  1. Ověření identity s exp. Návštěvník musí být přihlášený s platným userHash a exp (Identifikace návštěvníků). Bez exp se považuje za potvrzeného, ale ne aktuálního – pak se nic nenačítá.
  2. Nastavení v účtu v části Projekt → Zákaznická data: vybrat režim, zadat endpoint, povolit pole, nastavit upozornění pro nepřihlášené návštěvníky.

Režimy:

Požadavek

reactive.chat volá váš endpoint pomocí 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"}

Hlavičky:

Tělo:

Váš endpoint musí:

  1. ověřit podpis nad surovým tělem (v konstantním čase, např. hash_equals) – nikoli nad znovu zakódovaným JSON;
  2. odmítnout požadavky, jejichž X-RC-Zeit se od vašich hodin liší o více než 300 sekund;
  3. přijmout každé nonce pouze jednou (doporučeno);
  4. vracet pouze data daného user_id;
  5. být dostupný přes https na portu 443 na veřejné adrese – bez přesměrování, bez uživatelského jména/hesla v adrese;
  6. úplně odpovědět do 3 sekund (navázání spojení nejvýše 2 sekundy).

Odpověď

Přesně HTTP 200 s objektem JSON. Žádná shoda znamená {"bestellungen":[]}, také s 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" }
    }
  ]
}

Pole pro každou objednávku:

Limity: nejvýše 10 objednávek, 20 položek na objednávku, texty nejvýše 200 znaků, celá odpověď nejvýše 16 384 bajtů. Neznámá pole a pole nepovolená v účtu se zahodí bez chyby.

Nikdy se nepřebírají, ani když je odešlete: ulice a další řádky adresy, jména, e-mail, telefon, IBAN/BIC/údaje účtu a karet, datum narození, hesla/tokeny/klíče, daňová identifikační čísla a čísla průkazů totožnosti. Nejlépe je vůbec neodesílat.

Co se děje s odpovědí: použije se pouze pro jednu odpověď asistenta a neukládá se – ani do konverzace, ani do znalostní báze; protokol zaznamenává jen počet a dobu trvání. Pokud váš endpoint neodpoví včas nebo odpoví chybou, asistent odpoví, jako by žádná data nebyla.

Referenční endpoint v 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]);

Referenční endpoint v 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) });
});

Testování připojení

Otestovat připojení v účtu zavolá váš endpoint s user_id rc-test a anliegen test. Vraťte pro něj prázdný seznam nebo ukázkovou objednávku. Pokud volání selže, účet uvede jeden z těchto důvodů:

Import přes API

Pokud váš e-shop nemůže nabídnout endpoint, odesíláte data k nám. Potřebujete klíč API s rozsahem pouze import zákaznických dat (kundendaten:schreiben) a režim Import přes API nebo Obojí.

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"}]}'

Importovaná data se ukládají šifrovaně (zákaznické číslo pouze jako hash) a mažou 90 dnů po posledním importu. Proto průběžně odesílejte změny, například při každé změně stavu.

Chyby: 402 plan_required (bez placeného tarifu), 409 import_disabled (režim neumožňuje import, s kd_modus), 400 bad_request s param (user_id, project_id, bestellungen), 404 (cizí projekt). Obecný formát chyb popisuje Serverové API.

Nepřihlášení návštěvníci

Pokud se někdo bez potvrzené identity zeptá na objednávku, asistent nic nenačte; místo toho zobrazí vaše upozornění z účtu (text pro každý jazyk, volitelně s odkazem na přihlášení) nebo předá konverzaci vašemu týmu.

Dále: Webhooky · Serverové API

آخر تحديث: