Endpoint de dados de clientes: pedidos, entregas e faturas
Referência técnica da conexão de dados de clientes: requisição assinada do reactive.chat à sua loja, esquema de resposta, limites, motivos de falha, endpoint de referência completo em PHP e Node e importação pela API.
Com dados de clientes, o assistente de IA responde a perguntas como “Onde está minha encomenda?” ou “Minha fatura foi paga?”, apenas para visitantes confirmados e somente sobre seus próprios pedidos. Esta página é a referência técnica para quem desenvolve o endpoint. O que o assistente mostra e quais campos você habilita na conta está descrito em Dados de clientes no chat.
Disponível nos planos pagos (inclusive durante o período de teste).
Requisitos
- Verificação de identidade com exp. O visitante precisa estar conectado com
userHashválido eexp(Identificar visitantes). Semexp, conta como confirmado, mas não como atual; nesse caso, nada é consultado. - Configuração na conta, em Projeto → Dados de clientes: escolher o modo, informar o endpoint, habilitar campos e definir o aviso para visitantes não conectados.
Modos:
- Consulta – o reactive.chat consulta seu endpoint a cada pergunta pertinente. A resposta não é armazenada. Recomendado.
- Importação – sua loja envia os pedidos de um cliente pela API; são armazenados de forma criptografada do nosso lado por no máximo 90 dias.
- Ambos – primeiro os dados importados; se nada for encontrado, a consulta.
- Desativado.
A requisição
O reactive.chat chama seu endpoint com 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"}
Cabeçalhos:
- X-RC-Zeit – segundos Unix no momento da requisição.
- X-RC-Signatur –
hex(HMAC_SHA256(endpoint_secret, X-RC-Zeit + "." + raw_body)), 64 caracteres em minúsculas, sem prefixo. A chave é o segredo do endpoint da conta como string (começa comrckd_).
Corpo:
- user_id (string) – o ID confirmado de
identify. Nunca algo obtido do chat. - anliegen (string) –
bestellung(pedido),lieferung(entrega),rechnung(fatura),retoure(devolução);testpara o botão de teste. - referenz (string ou
null) – um número mencionado pelo cliente no chat. Apenas um filtro entre os pedidos desseuser_id; um número de outra pessoa não deve encontrar nada. - sprache (string) – duas letras, por exemplo,
de. - nonce (string) – 32 caracteres hexadecimais, novos a cada requisição.
Seu endpoint deve:
- verificar a assinatura sobre o corpo bruto (em tempo constante, por exemplo,
hash_equals), não sobre JSON recodificado; - rejeitar requisições cujo
X-RC-Zeitdifira do relógio em mais de 300 segundos; - aceitar cada
nonceapenas uma vez (recomendado); - retornar apenas dados do
user_idinformado; - ser acessível por
httpsna porta 443 em um endereço público, sem redirecionamentos nem nome de usuário/senha no endereço; - responder completamente em 3 segundos (estabelecimento da conexão em no máximo 2 segundos).
A resposta
Exatamente HTTP 200 com um objeto JSON. Sem correspondência: {"bestellungen":[]}, também com 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 (string, obrigatório) – um pedido sem número é descartado.
- datum (string,
YYYY-MM-DD). - status (string) – status do pedido como texto simples.
- summe (string ou número), waehrung (ISO 4217, por exemplo,
EUR). - positionen (lista, no máximo 20) – cada um com
nameemenge. - versand (objeto) –
dienst,sendungsnummer,status, opcionalmenteorteplz(somente se explicitamente habilitados na conta). - rechnung (objeto) –
nummer,status,faellig_am.
Limites: no máximo 10 pedidos, 20 itens por pedido, textos de até 200 caracteres, resposta inteira de até 16.384 bytes. Campos desconhecidos e não habilitados na conta são descartados sem erro.
Nunca são aceitos, mesmo que você envie: rua e demais linhas do endereço, nomes, e-mail, telefone, IBAN/BIC/conta e dados de cartão, data de nascimento, senhas/tokens/chaves, números fiscais e de documentos de identidade. O melhor é nem enviá-los.
O que acontece com a resposta: ela é usada apenas para aquela resposta do assistente e não é armazenada, nem na conversa nem na base de conhecimento; o log registra apenas quantidade e duração. Se o endpoint não responder a tempo ou retornar um erro, o assistente responde como se não houvesse dados.
Endpoint de referência em 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 referência em 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) });
});
Testar a conexão
Testar conexão na conta chama seu endpoint com user_id rc-test e anliegen test. Retorne uma lista vazia ou um pedido de exemplo. Se a chamada falhar, a conta informa um destes motivos:
- kein_endpunkt, kein_geheimnis – falta algo na conta.
- url_ungueltig, nur_https, port_verboten, zugangsdaten_in_url – o endereço não é permitido (somente
https, porta 443, sem credenciais no endereço). - host_unaufloesbar, adresse_privat, adresse_gewechselt – o nome não é resolvido ou aponta para um endereço privado.
- tls, verbindung, zeitueberschreitung – certificado, conexão ou prazo de 3 segundos.
- umleitung – seu endpoint responde com 3xx.
- http_<code> – um status diferente de 200 (por exemplo,
http_401: sua verificação de assinatura rejeita a requisição). - antwort_zu_gross, antwort_kein_json, antwort_ohne_bestellungen – a resposta não corresponde ao esquema.
- tarif – o projeto não está em um plano pago.
Importar pela API
Se sua loja não puder oferecer um endpoint, você envia os dados para nós. Precisa de uma chave de API com o escopo só importação de dados de clientes (kundendaten:schreiben) e o modo Importação ou 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} – substitui todos os pedidos desse cliente (no máximo 50; uma lista vazia os exclui). Resposta
{"data":{"stored":1,"dropped":0,"max_orders":50,"expires_in_days":90}}. - DELETE /v1/kundendaten/{user_id} – remove os pedidos; sempre funciona, mesmo sem plano. Resposta
{"data":{"deleted":1}}. - project_id – obrigatório quando o espaço de trabalho tem mais de um projeto.
- user_id – o mesmo ID que você assina em
identify; codificado para URL, no máximo 200 caracteres. Uma barra codificada (%2F) não é possível. - Não há
GET.
Os dados importados são armazenados de forma criptografada (o número do cliente apenas como hash) e excluídos 90 dias após a última importação. Por isso, envie as alterações continuamente, por exemplo, a cada mudança de status.
Erros: 402 plan_required (sem plano pago), 409 import_disabled (o modo não permite importação, com kd_modus), 400 bad_request com param (user_id, project_id, bestellungen), 404 (projeto de outra pessoa). O formato geral de erro está descrito em API do servidor.
Visitantes não conectados
Se alguém sem identidade confirmada perguntar sobre um pedido, o assistente não consulta nada; em vez disso, mostra o aviso da conta (texto por idioma, opcionalmente um link de login) ou transfere para sua equipe.
A seguir: Webhooks · API do servidor
Viimati uuendatud: