Como podemos ajudar?

Conte brevemente do que se trata; responderemos por e-mail.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Para a página inicial

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

  1. Verificação de identidade com exp. O visitante precisa estar conectado com userHash válido e exp (Identificar visitantes). Sem exp, conta como confirmado, mas não como atual; nesse caso, nada é consultado.
  2. 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:

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:

Corpo:

Seu endpoint deve:

  1. verificar a assinatura sobre o corpo bruto (em tempo constante, por exemplo, hash_equals), não sobre JSON recodificado;
  2. rejeitar requisições cujo X-RC-Zeit difira do relógio em mais de 300 segundos;
  3. aceitar cada nonce apenas uma vez (recomendado);
  4. retornar apenas dados do user_id informado;
  5. ser acessível por https na porta 443 em um endereço público, sem redirecionamentos nem nome de usuário/senha no endereço;
  6. 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:

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:

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

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

Última atualização: