ہم کیسے مدد کر سکتے ہیں؟

مختصراً بتائیں کہ معاملہ کیا ہے—ہم آپ کو ای میل کے ذریعے جواب دیں گے۔

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
ابتدائی صفحے پر جائیں

Kundendaten-Endpunkt: Bestellungen, Versand und Rechnungen

Technische Referenz für die Kundendaten-Anbindung – der signierte Abruf von reactive.chat an deinen Shop, Antwortschema, Grenzen, Fehlergründe, ein vollständiger Referenz-Endpunkt in PHP und Node sowie der Import per API.

Mit Kundendaten beantwortet der KI-Assistent Fragen wie „Wo ist mein Paket?“ oder „Ist meine Rechnung bezahlt?“ – nur für bestätigte Besucher und nur zu ihren eigenen Bestellungen. Diese Seite ist die technische Referenz für die Person, die den Endpunkt baut. Was der Assistent zeigt und welche Felder du im Konto freigibst, beschreibt Kundendaten im Chat.

Verfügbar in den bezahlten Tarifen (auch in der Testphase).

Voraussetzungen

  1. Identitätsprüfung mit exp. Der Besucher muss mit gültigem userHash und exp angemeldet sein (Besucher identifizieren). Ohne exp gilt er als bestätigt, aber nicht als frisch – dann wird nichts abgerufen.
  2. Einstellung im Konto unter Projekt → Kundendaten: Betriebsart wählen, Endpunkt eintragen, Felder freigeben, Hinweis für nicht angemeldete Besucher festlegen.

Betriebsarten:

Der Abruf

reactive.chat ruft deinen Endpunkt mit POST auf:

POST /rc-kundendaten HTTP/1.1
Host: shop.deine-firma.de
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":"de","nonce":"9f86d081884c7d659a2feaa0c55ad015"}

Kopfzeilen:

Rumpf:

Dein Endpunkt muss:

  1. die Signatur über den rohen Body prüfen (zeitkonstant, z. B. hash_equals) – nicht über ein neu kodiertes JSON;
  2. Anfragen ablehnen, deren X-RC-Zeit mehr als 300 Sekunden von deiner Uhr abweicht;
  3. jede nonce nur einmal annehmen (empfohlen);
  4. nur Daten der übergebenen user_id liefern;
  5. über https auf Port 443 unter einer öffentlichen Adresse erreichbar sein – ohne Weiterleitung, ohne Benutzername/Passwort in der Adresse;
  6. innerhalb von 3 Sekunden vollständig antworten (Verbindungsaufbau höchstens 2 Sekunden).

Die Antwort

Genau HTTP 200 mit einem JSON-Objekt. Kein Treffer ist {"bestellungen":[]}, ebenfalls mit 200.

{
  "bestellungen": [
    {
      "nummer": "2026-0815",
      "datum": "2026-09-28",
      "status": "versandt",
      "summe": "49.90",
      "waehrung": "EUR",
      "positionen": [ { "name": "Laufrad blau", "menge": 1 } ],
      "versand": { "dienst": "DHL", "sendungsnummer": "00340434161234567890", "status": "in Zustellung" },
      "rechnung": { "nummer": "R-2026-1201", "status": "bezahlt", "faellig_am": "2026-10-12" }
    }
  ]
}

Felder je Bestellung:

Grenzen: höchstens 10 Bestellungen, 20 Positionen je Bestellung, Texte höchstens 200 Zeichen, ganze Antwort höchstens 16 384 Bytes. Unbekannte und im Konto nicht freigegebene Felder werden ohne Fehlermeldung verworfen.

Nie übernommen, auch wenn du sie schickst: Straße und weitere Adresszeilen, Namen, E-Mail, Telefon, IBAN/BIC/Konto- und Kartendaten, Geburtsdatum, Passwörter/Tokens/Schlüssel, Steuer- und Ausweisnummern. Schick sie am besten gar nicht.

Was mit der Antwort passiert: Sie geht nur in die eine Antwort des Assistenten ein und wird nicht gespeichert – weder im Gespräch noch in der Wissensdatenbank; im Protokoll stehen nur Anzahl und Dauer. Antwortet dein Endpunkt nicht rechtzeitig oder fehlerhaft, antwortet der Assistent so, als lägen keine Daten vor.

Referenz-Endpunkt in PHP

<?php
declare(strict_types=1);

$GEHEIMNIS = (string) getenv('RC_KD_GEHEIMNIS');   // rckd_... aus dem Konto

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' => 'nur POST']); }

$body = (string) file_get_contents('php://input', false, null, 0, 4097);
if (strlen($body) > 4096) { antworte(413, ['fehler' => 'zu gross']); }

$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 nur einmal: 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 schon benutzt']);
}

if ($userId === 'rc-test') { antworte(200, ['bestellungen' => []]); }   // Testknopf

// NUR dieser Kunde; die Referenz ist nur ein 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]);

Referenz-Endpunkt 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 -> Zeit; in Produktion eine Datenbank

app.post('/rc-kundendaten', express.raw({ type: 'application/json', limit: '4kb' }), async (req, res) => {
  const body = req.body.toString('utf8');          // ROHER 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);   // NUR dieser Kunde
  res.json({ bestellungen: bestellungen.slice(0, 10) });
});

Verbindung testen

Verbindung testen im Konto ruft deinen Endpunkt mit user_id rc-test und anliegen test auf. Liefere dafür eine leere Liste oder eine Beispielbestellung. Scheitert der Aufruf, nennt das Konto einen dieser Gründe:

Import per API

Kann dein Shop keinen Endpunkt anbieten, schiebst du die Daten zu uns. Du brauchst einen API-Schlüssel mit der Rechtestufe Kundendaten-Import (kundendaten:schreiben) und die Betriebsart Import oder 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":"versandt"}]}'

Importierte Daten werden verschlüsselt abgelegt (die Kundennummer nur als Hash) und 90 Tage nach dem letzten Import gelöscht. Schicke Änderungen also laufend, etwa bei jedem Statuswechsel.

Fehler: 402 plan_required (kein bezahlter Tarif), 409 import_disabled (Betriebsart erlaubt keinen Import, mit kd_modus), 400 bad_request mit param (user_id, project_id, bestellungen), 404 (fremdes Projekt). Das allgemeine Fehlerformat steht unter Server-API.

Nicht angemeldete Besucher

Fragt jemand ohne bestätigte Identität nach einer Bestellung, ruft der Assistent nichts ab, sondern zeigt deinen Hinweis aus dem Konto (Text je Sprache, optional ein Link zur Anmeldung) oder übergibt an dein Team.

Weiter: Webhooks · Server-API

آخری بار تازہ کاری: