crm.md 10 KB

Integração de CRM — Guia de Envio de Dados

Este documento descreve como uma plataforma de CRM deve enviar dados para a nossa plataforma. O envio é feito por um único endpoint de webhook, autenticado por HMAC-SHA256, e os dados são gravados nas tabelas-fonte da empresa correspondente.


1. Visão geral

  • Existe um único endpoint que recebe todos os tipos de dado.
  • Cada requisição informa um campo type que define o que está sendo enviado (product, client ou sale) e um objeto data com os campos.
  • A autenticação é por segredo HMAC exclusivo de cada empresa. O segredo é entregue de forma segura para a empresa e nunca trafega na requisição.
  • A empresa de destino é identificada na URL ({companyId}). Os dados de uma empresa nunca se misturam com os de outra.

2. Endpoint

POST /v1/webhooks/crm/{companyId}
Item Valor
Método POST
Caminho /v1/webhooks/crm/{companyId}
{companyId} Identificador numérico da empresa (fornecido por nós).
Content-Type application/json
Header de assinatura X-Signature: sha256=<hmac>
Corpo JSON no formato do envelope (ver seção 4).

Cada empresa recebe o seu companyId e o seu segredo HMAC. Use sempre o par correto: o segredo de uma empresa só é válido para o companyId dela.


3. Autenticação (HMAC-SHA256)

Cada requisição precisa provar que é autêntica e não foi adulterada. Para isso:

  1. Monte o corpo JSON da requisição (a string exata que será enviada).
  2. Calcule HMAC_SHA256(corpo, segredo) em hexadecimal.
  3. Envie o resultado no header X-Signature, no formato sha256=<hex>.

Nós recalculamos a assinatura do corpo recebido com o segredo da empresa e comparamos. Se não bater, a requisição é rejeitada com 401.

Regras importantes

  • A assinatura é calculada sobre o corpo exato em bytes (o mesmo que vai no --data). Qualquer diferença (espaço, quebra de linha, reordenação) muda a assinatura. Assine exatamente o que enviar.
  • O prefixo sha256= é aceito (e recomendado); enviar só o hex também funciona.
  • O segredo é secreto: nunca o coloque na URL, em logs ou no corpo.

Exemplos de geração da assinatura

bash / openssl

SECRET="<SEU_SEGREDO>"
BODY='{"type":"product","data":{"name":"Plano Pro","value":199.90}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')

curl -X POST "https://<host>/v1/webhooks/crm/3" \
  -H "Content-Type: application/json" \
  -H "X-Signature: sha256=$SIG" \
  --data "$BODY"

Node.js

const crypto = require('crypto');
const body = JSON.stringify({ type: 'product', data: { name: 'Plano Pro', value: 199.9 } });
const sig = crypto.createHmac('sha256', SECRET).update(body).digest('hex');
// header: 'X-Signature': `sha256=${sig}`   | enviar exatamente `body` no POST

PHP

$body = json_encode(['type' => 'product', 'data' => ['name' => 'Plano Pro', 'value' => 199.90]]);
$sig  = hash_hmac('sha256', $body, $secret);
// header: "X-Signature: sha256=$sig"       | enviar exatamente $body no POST

Python

import hmac, hashlib, json
body = json.dumps({"type": "product", "data": {"name": "Plano Pro", "value": 199.90}})
sig = hmac.new(secret.encode(), body.encode(), hashlib.sha256).hexdigest()
# header: {"X-Signature": f"sha256={sig}"}  | enviar exatamente `body` no POST

4. Formato do corpo (envelope)

Todo corpo segue o mesmo formato:

{
  "type": "product | client | sale",
  "data": { ... campos conforme o type ... }
}
  • type (string, obrigatório): o tipo do dado.
  • data (objeto, obrigatório): os campos daquele tipo.

5. Regras gerais de campos

  • Campos essenciais são obrigatórios. Se faltarem (ou vierem vazios), a requisição é rejeitada com 400.
  • Campos opcionais podem vir como "" ou ser omitidos — nesse caso assumem um valor neutro (string vazia, 0, ou um valor derivado).
  • Valores monetários aceitam número ou string numérica (ex.: 199.90 ou "199.90"); valores não numéricos viram 0.
  • Datas devem estar em formato reconhecível (ISO 8601 recomendado, ex.: 2026-06-11T14:30:00).

6. Tipos de dado

6.1 product — Produto / SKU

Cadastra ou atualiza um produto. A identidade é o nome dentro da empresa: se já existe um produto com o mesmo name, ele é atualizado; senão, é criado.

Campo Obrigatório Tipo Descrição / comportamento
name string Nome do produto. Identidade do SKU.
value número Preço. Vazio/ausente → 0.
line string Linha/categoria. Vazio/ausente → "".
sold inteiro Quantidade vendida. Vazio/ausente → 0. Sobrescreve o valor atual (ver aviso abaixo).

⚠️ Sobre sold: o product substitui o contador sold pelo valor enviado. Se você omitir sold, ele vira 0. Já o tipo sale incrementa esse contador a cada venda. Para não zerar a contagem sem querer, escolha uma estratégia: ou você gerencia sold só pelo product (mandando sempre o total acumulado), ou deixa o sold por conta dos eventos sale e não envia sold em atualizações de produto.

Exemplo

{
  "type": "product",
  "data": {
    "name": "Plano Pro",
    "value": 199.90,
    "line": "Assinaturas",
    "sold": 0
  }
}

Resposta: S_CREATED (criado) ou S_OK (atualizado), com data.sku_id.


6.2 client — Cliente

Cadastra ou atualiza um cliente. A identidade é o telefone dentro da empresa (company_id + phone): mesmo telefone → atualiza; senão → cria.

Campo Obrigatório Tipo Descrição / comportamento
phone string Telefone. Chave única do cliente.
name string Nome. Vazio/ausente → "".
email string E-mail. Vazio/ausente → "".
segment string Segmento. Vazio/ausente → "".
provider_id string ID do cliente no CRM. Vazio/ausente → derivado como crm:<phone>.

Exemplo

{
  "type": "client",
  "data": {
    "phone": "+5511999990001",
    "name": "Maria Silva",
    "email": "maria@exemplo.com",
    "segment": "Premium",
    "provider_id": "CRM-CLI-4521"
  }
}

Resposta: S_OK, com data.client_id.


6.3 sale — Venda

Registra uma venda no histórico. Cada venda é um evento com data, o que permite apurar faturamento por período. É idempotente pela chave company_id + external_id: reenviar a mesma venda não duplica o faturamento.

Campo Obrigatório Tipo Descrição / comportamento
external_id string ID da venda no CRM. Garante a idempotência.
amount número Valor faturado. Deve ser numérico.
occurred_at data/hora Quando a venda ocorreu (ISO 8601). Base do histórico.
product_name string Nome do produto vendido. Se casar com um SKU, vincula e incrementa sold. Sem match → venda sem produto vinculado.
quantity inteiro Quantidade. Vazio/ausente → 1.
client_phone string Telefone do cliente. Se casar, vincula a venda ao cliente.
operator_email string E-mail do operador/vendedor. Se casar, vincula ao operador.

Comportamento da idempotência

  • Venda nova → resposta S_CREATED com data.sale_id.
  • Reenvio do mesmo external_id → resposta S_OK ("Sale already recorded.") com data.sale_id: 0. O faturamento e o sold não são contados de novo.

Exemplo

{
  "type": "sale",
  "data": {
    "external_id": "PEDIDO-2026-0001",
    "amount": 199.90,
    "occurred_at": "2026-06-11T14:30:00",
    "product_name": "Plano Pro",
    "quantity": 1,
    "client_phone": "+5511999990001",
    "operator_email": "vendedor@empresa.com"
  }
}

Resposta: S_CREATED ou S_OK, com data.sale_id.


7. Respostas e códigos de status

Toda resposta segue o formato padrão:

{
  "status": "ok | failed",
  "code": "S_OK | S_CREATED | E_VALIDATE | E_NOT_FOUND | E_GENERIC",
  "message": "mensagem legível",
  "data": { ... }   // presente quando há dados
}
HTTP code Quando acontece
200 S_OK Processado com sucesso (atualização ou reenvio idempotente).
200 S_CREATED Registro novo criado.
400 E_VALIDATE JSON inválido, falta type/data, campo essencial ausente, type desconhecido, ou companyId inválido.
401 E_VALIDATE Assinatura HMAC inválida ou ausente.
404 E_NOT_FOUND companyId não corresponde a nenhuma empresa ativa.
500 E_GENERIC Falha interna ao processar. Pode reenviar.

Exemplos de erro:

{ "status": "failed", "code": "E_VALIDATE", "message": "Invalid signature" }
{ "status": "failed", "code": "E_VALIDATE", "message": "sale requires a numeric \"amount\"" }
{ "status": "failed", "code": "E_NOT_FOUND", "message": "Unknown company" }

8. Boas práticas e observações

  • Ordem de envio: envie product e client antes das sale que os referenciam, para que a venda já consiga vincular o produto/cliente.
  • Reenvio seguro: vendas têm idempotência por external_id. Em caso de timeout ou erro de rede, pode reenviar a mesma venda sem medo de duplicar.
  • O que NÃO enviar: não enviamos/recebemos métricas calculadas (churn, LTV, ticket médio, faturamento consolidado). Essas são derivadas internamente a partir dos dados crus (product, client, sale). Mande apenas os dados brutos.
  • Datas: prefira ISO 8601 com hora (2026-06-11T14:30:00). Se o fuso importar, inclua-o.
  • Segredo HMAC: se suspeitar de vazamento, solicite a rotação do segredo da empresa.

9. Checklist de integração

  • Recebi o companyId e o segredo HMAC da empresa.
  • Monto o corpo JSON e calculo HMAC_SHA256(corpo, segredo).
  • Envio X-Signature: sha256=<hex> e Content-Type: application/json.
  • Assino exatamente o corpo que envio (sem reserializar depois).
  • Uso type = product | client | sale com os campos essenciais.
  • Trato os códigos de status (200/400/401/404/500).
  • Reenvio vendas com o mesmo external_id em caso de falha.