איך אפשר לעזור?

אפשר לתאר בקצרה במה מדובר, ונחזור בדוא״ל.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
לעמוד הבית

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

עדכון אחרון: