Endpoint de datos de clientes: pedidos, envíos y facturas
Referencia técnica de la conexión de datos de clientes: solicitud firmada de reactive.chat a su tienda, esquema de respuesta, límites, motivos de fallo, endpoint de referencia completo en PHP y Node e importación mediante API.
Con los datos de clientes, el asistente de IA responde a preguntas como «¿Dónde está mi paquete?» o «¿Está pagada mi factura?», solo para visitantes confirmados y solo sobre sus propios pedidos. Esta página es la referencia técnica para quien construye el endpoint. Qué muestra el asistente y qué campos habilita en su cuenta se describe en Datos de clientes en el chat.
Disponible en los planes de pago (también durante la prueba).
Requisitos
- Verificación de identidad con exp. El visitante debe estar identificado con un
userHashválido yexp(Identificar visitantes). Sinexpse considera confirmado, pero sin verificación vigente; en ese caso no se consulta nada. - Ajuste en su cuenta, en Proyecto → Datos de clientes: elegir el modo, introducir el endpoint, habilitar campos y establecer el aviso para visitantes sin sesión iniciada.
Modos:
- Consulta: reactive.chat consulta su endpoint para cada pregunta pertinente. La respuesta no se almacena. Recomendado.
- Importación: su tienda nos envía los pedidos de un cliente mediante la API; se almacenan cifrados en nuestros sistemas durante un máximo de 90 días.
- Ambos: primero los datos importados; si no se encuentra nada, se hace la consulta.
- Desactivado.
La solicitud
reactive.chat llama a su endpoint con 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"}
Cabeceras:
- X-RC-Zeit: segundos Unix en el momento de la solicitud.
- X-RC-Signatur:
hex(HMAC_SHA256(endpoint_secret, X-RC-Zeit + "." + raw_body)), 64 caracteres en minúsculas, sin prefijo. La clave es el secreto del endpoint de su cuenta como cadena (empieza porrckd_).
Cuerpo:
- user_id (cadena): el identificador confirmado de
identify. Nunca un dato extraído del chat. - anliegen (cadena):
bestellung(pedido),lieferung(entrega),rechnung(factura),retoure(devolución);testpara el botón de prueba. - referenz (cadena o
null): un número que el cliente mencionó en el chat. Solo es un filtro dentro de los pedidos de esteuser_id; un número ajeno no debe devolver nada. - sprache (cadena): dos letras, por ejemplo,
de. - nonce (cadena): 32 caracteres hexadecimales, nuevo para cada solicitud.
Su endpoint debe:
- verificar la firma sobre el cuerpo sin procesar (en tiempo constante, por ejemplo, con
hash_equals), no sobre JSON recodificado; - rechazar solicitudes cuyo
X-RC-Zeitdifiera más de 300 segundos de su reloj; - aceptar cada
nonceuna sola vez (recomendado); - devolver únicamente datos del
user_idindicado; - ser accesible mediante
httpsen el puerto 443, en una dirección pública, sin redirecciones ni nombre de usuario/contraseña en la dirección; - responder por completo en 3 segundos (establecimiento de conexión en un máximo de 2 segundos).
La respuesta
Exactamente HTTP 200 con un objeto JSON. Si no hay coincidencias, {"bestellungen":[]}, también con 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" }
}
]
}
Campos por pedido:
- nummer (cadena, obligatorio): se descarta un pedido sin número.
- datum (cadena,
YYYY-MM-DD). - status (cadena): estado del pedido como texto sin formato.
- summe (cadena o número), waehrung (ISO 4217, por ejemplo,
EUR). - positionen (lista, máximo 20): cada elemento con
nameymenge. - versand (objeto):
dienst,sendungsnummer,status; opcionalmenteortyplz(solo si los habilita expresamente en su cuenta). - rechnung (objeto):
nummer,status,faellig_am.
Límites: un máximo de 10 pedidos, 20 artículos por pedido, textos de hasta 200 caracteres y una respuesta completa de un máximo de 16 384 bytes. Los campos desconocidos y los no habilitados en su cuenta se descartan sin error.
Nunca se incorporan, aunque los envíe: calle y otras líneas de dirección, nombres, correo electrónico, teléfono, IBAN/BIC/cuenta y datos de tarjeta, fecha de nacimiento, contraseñas/tokens/claves, números de identificación fiscal y de documentos de identidad. Es preferible no enviarlos.
Qué ocurre con la respuesta: se utiliza solo para esa única respuesta del asistente y no se almacena, ni en la conversación ni en la base de conocimiento; el registro solo recoge la cantidad y la duración. Si su endpoint no responde a tiempo o responde con un error, el asistente contesta como si no hubiera datos.
Endpoint de referencia en 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]);
Endpoint de referencia en 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) });
});
Probar la conexión
Probar la conexión, en su cuenta, llama a su endpoint con user_id rc-test y anliegen test. Devuelva una lista vacía o un pedido de ejemplo. Si falla la llamada, su cuenta indica uno de estos motivos:
- kein_endpunkt, kein_geheimnis: falta algo en su cuenta.
- url_ungueltig, nur_https, port_verboten, zugangsdaten_in_url: dirección no permitida (solo
https, puerto 443, sin credenciales en la dirección). - host_unaufloesbar, adresse_privat, adresse_gewechselt: el nombre no se resuelve o se resuelve a una dirección privada.
- tls, verbindung, zeitueberschreitung: certificado, conexión o plazo de 3 segundos.
- umleitung: su endpoint responde con 3xx.
- http_<code>: estado distinto de 200 (por ejemplo,
http_401: su comprobación de firma rechaza la solicitud). - antwort_zu_gross, antwort_kein_json, antwort_ohne_bestellungen: la respuesta no coincide con el esquema.
- tarif: el proyecto no tiene un plan de pago.
Importación mediante API
Si su tienda no puede ofrecer un endpoint, envíenos los datos. Necesita una clave de API con el alcance solo importación de datos de clientes (kundendaten:schreiben) y el modo Importación o Ambos.
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}: sustituye todos los pedidos de este cliente (un máximo de 50; una lista vacía los elimina). Respuesta
{"data":{"stored":1,"dropped":0,"max_orders":50,"expires_in_days":90}}. - DELETE /v1/kundendaten/{user_id}: los elimina; siempre funciona, incluso sin plan. Respuesta
{"data":{"deleted":1}}. - project_id: obligatorio en cuanto su espacio de trabajo tenga más de un proyecto.
- user_id: el mismo identificador que firma en
identify; codificado para URL, con un máximo de 200 caracteres. No se admite una barra codificada (%2F). - No existe
GET.
Los datos importados se almacenan cifrados (el número de cliente solo como hash) y se eliminan 90 días después de la última importación. Por eso, envíe los cambios continuamente, por ejemplo, con cada cambio de estado.
Errores: 402 plan_required (sin plan de pago), 409 import_disabled (el modo no permite importar, con kd_modus), 400 bad_request con param (user_id, project_id, bestellungen), 404 (proyecto ajeno). El formato general de errores se describe en API de servidor.
Visitantes sin sesión iniciada
Si alguien sin identidad confirmada pregunta por un pedido, el asistente no consulta nada; en su lugar muestra el aviso de su cuenta (texto por idioma y, opcionalmente, un enlace para iniciar sesión) o transfiere la conversación a su equipo.
Siguiente: Webhooks · API de servidor
Síðast uppfært: