Nola lagun zaitzakegu?

Esaguzu labur zertaz ari zaren, eta posta elektronikoz erantzungo dizugu.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Itzuli hasierako orrira

Endpoint danych klientów: zamówienia, wysyłka i faktury

Dokumentacja techniczna integracji danych klientów – podpisane zapytanie reactive.chat do twojego sklepu, schemat odpowiedzi, limity, przyczyny błędów, kompletny endpoint referencyjny w PHP i Node oraz import przez API.

Dzięki danym klientów asystent AI odpowiada na pytania takie jak „Gdzie jest moja paczka?” albo „Czy moja faktura jest opłacona?” – tylko potwierdzonym odwiedzającym i tylko na temat ich własnych zamówień. Ta strona jest dokumentacją techniczną dla osoby, która buduje endpoint. Co pokazuje asystent i jakie pola udostępniasz w koncie, opisuje Dane klientów w czacie.

Dostępne w płatnych planach (także w okresie próbnym).

Wymagania

  1. Weryfikacja tożsamości z exp. Odwiedzający musi być zalogowany z prawidłowym userHash i exp (Identyfikacja odwiedzających). Bez exp uchodzi za potwierdzonego, ale nie za świeżego – wtedy nic nie jest pobierane.
  2. Ustawienie w koncie w sekcji Projekt → Dane klientów: wybierz tryb, wpisz endpoint, udostępnij pola, ustal komunikat dla niezalogowanych odwiedzających.

Tryby:

Pobieranie

reactive.chat wywołuje twój endpoint metodą POST:

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

Nagłówki:

Treść:

Twój endpoint musi:

  1. sprawdzać podpis na surowej treści (w stałym czasie, np. hash_equals) – a nie na ponownie zakodowanym JSON;
  2. odrzucać żądania, których X-RC-Zeit różni się od twojego zegara o ponad 300 sekund;
  3. przyjmować każdy nonce tylko raz (zalecane);
  4. zwracać wyłącznie dane przekazanego user_id;
  5. być dostępny przez https na porcie 443 pod publicznym adresem – bez przekierowania, bez nazwy użytkownika/hasła w adresie;
  6. odpowiedzieć w całości w ciągu 3 sekund (nawiązanie połączenia najwyżej 2 sekundy).

Odpowiedź

Dokładnie HTTP 200 z obiektem JSON. Brak trafienia to {"bestellungen":[]}, również z 200.

{
  "bestellungen": [
    {
      "nummer": "2026-0815",
      "datum": "2026-09-28",
      "status": "wysłane",
      "summe": "49.90",
      "waehrung": "EUR",
      "positionen": [ { "name": "Rowerek biegowy niebieski", "menge": 1 } ],
      "versand": { "dienst": "DHL", "sendungsnummer": "00340434161234567890", "status": "w doręczeniu" },
      "rechnung": { "nummer": "R-2026-1201", "status": "opłacona", "faellig_am": "2026-10-12" }
    }
  ]
}

Pola zamówienia:

Limity: najwyżej 10 zamówień, 20 pozycji na zamówienie, teksty najwyżej 200 znaków, cała odpowiedź najwyżej 16 384 bajty. Nieznane i nieudostępnione w koncie pola są odrzucane bez komunikatu o błędzie.

Nigdy nie przejmowane, nawet jeśli je wyślesz: ulica i kolejne linie adresu, imiona i nazwiska, e-mail, telefon, IBAN/BIC/dane konta i karty, data urodzenia, hasła/tokeny/klucze, numery podatkowe i numery dokumentów tożsamości. Najlepiej w ogóle ich nie wysyłaj.

Co dzieje się z odpowiedzią: trafia tylko do tej jednej odpowiedzi asystenta i nie jest zapisywana – ani w rozmowie, ani w bazie wiedzy; w logu widnieją tylko liczba i czas trwania. Jeśli twój endpoint nie odpowie na czas albo odpowie błędnie, asystent odpowiada tak, jakby nie było danych.

Endpoint referencyjny w PHP

<?php
declare(strict_types=1);

$GEHEIMNIS = (string) getenv('RC_KD_GEHEIMNIS');   // rckd_... z konta

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

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

$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 tylko raz: 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 już użyty']);
}

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

// TYLKO ten klient; referencja jest tylko filtrem
$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]);

Endpoint referencyjny w 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 -> czas; na produkcji baza danych

app.post('/rc-kundendaten', express.raw({ type: 'application/json', limit: '4kb' }), async (req, res) => {
  const body = req.body.toString('utf8');          // SUROWA treść
  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);   // TYLKO ten klient
  res.json({ bestellungen: bestellungen.slice(0, 10) });
});

Testowanie połączenia

Testuj połączenie w koncie wywołuje twój endpoint z user_id rc-test i anliegen test. Zwróć wtedy pustą listę albo przykładowe zamówienie. Jeśli wywołanie się nie powiedzie, konto podaje jedną z tych przyczyn:

Import przez API

Jeśli twój sklep nie może udostępnić endpointu, przesyłasz dane do nas. Potrzebujesz klucza API z zakresem tylko import danych klientów (kundendaten:schreiben) i trybu Import przez API albo Oba.

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":"wysłane"}]}'

Zaimportowane dane są przechowywane w postaci zaszyfrowanej (numer klienta tylko jako hash) i usuwane 90 dni po ostatnim imporcie. Wysyłaj więc zmiany na bieżąco, np. przy każdej zmianie statusu.

Błędy: 402 plan_required (brak płatnego planu), 409 import_disabled (tryb nie pozwala na import, z kd_modus), 400 bad_request z param (user_id, project_id, bestellungen), 404 (cudzy projekt). Ogólny format błędów opisuje API serwera.

Niezalogowani odwiedzający

Jeśli ktoś bez potwierdzonej tożsamości zapyta o zamówienie, asystent niczego nie pobiera, tylko pokazuje twój komunikat z konta (tekst w każdym języku, opcjonalnie link do logowania) albo przekazuje rozmowę twojemu zespołowi.

Dalej: Webhooki · API serwera

Azken eguneratzea: