Documentação da API
API REST para sincronizar dados estruturados com o seu bot da Bravos AI.
Introdução
A Webhook API permite sincronizar dados estruturados (JSON ou XML) com um bot da Bravos AI. Você pode fazer uma carga inicial a partir de uma URL e manter os dados atualizados por meio de webhooks HTTP assinados com HMAC-SHA256.
Configuração
Antes de enviar webhooks, você precisa configurar a conexão em Integrações > Webhook personalizado no painel de administração.
- 1
Crie a conexão
Dê a ela um nome descritivo (ex.: "Clínicas", "Inventário") e informe a URL onde estão os seus dados em formato JSON ou XML (máximo de 50 MB). O sistema baixará os dados e detectará os campos automaticamente.
- 2
Escolha o campo identificador
Depois de criar a conexão, o sistema detecta os campos dos seus dados. Selecione qual é o identificador único de cada registro (equivalente a uma chave primária). Por exemplo:
id,ref,sku. Este campo é obrigatório em cada item que você enviar por webhook. - 3
Ative e recolha suas credenciais
Ao ativar, o sistema processa a carga inicial a partir da sua URL. A partir desse momento você tem disponível:
- Endpoint — a URL para a qual enviar os webhooks
- Secret — a chave para assinar cada request com HMAC-SHA256
- Dataset slug e Bot ID — já incluídos no endpoint
Depois de ativada, você pode excluir campos pelo painel para que o chatbot não os use nas respostas.
Autenticação
Cada request deve incluir uma assinatura HMAC-SHA256 no header X-Bravos-Signature. Ela é calculada sobre o body completo usando o seu secret. O formato é hex em minúsculas.
import hmac, hashlib
secret = "SEU_SECRET_AQUI"
body = b'{"action":"upsert","items":[...]}'
signature = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
# Resultado: "a1b2c3d4..." (hex lowercase)Se a assinatura não coincidir, o servidor responde 401. Depois de 10 tentativas com falha em um minuto, o endpoint é bloqueado temporariamente.
Endpoint
POST https://api.bravos-ai.com/integrations/custom-webhook/{bot_id}/{dataset_slug}/syncOs valores de bot_id e dataset_slug são gerados ao criar a conexão e ficam visíveis no painel de administração.
Headers obrigatórios
Content-Type: application/json
X-Bravos-Signature: <assinatura HMAC-SHA256 em hex>
Criar e atualizar dados
Envie "action": "upsert" com um array de itens. Se um item com o mesmo identificador já existe, ele é atualizado. Se não existe, é criado.
{
"action": "upsert",
"items": [
{
"ref": "SP-01",
"nome": "Clínica São Paulo Centro",
"endereco": "Av. Paulista, 1000",
"telefone": "+55 11 3000-0000",
"especialidades": ["Odontologia", "Ortopedia", "Dermatologia"],
"horario": {
"segunda_sexta": "09:00 - 20:00",
"sabados": "10:00 - 14:00"
},
"servicos": [
{"nome": "Limpeza dental", "preco": 250, "duracao_min": 45},
{"nome": "Radiografia panorâmica", "preco": 150, "duracao_min": 15},
{"nome": "Consulta dermatológica", "preco": 350, "duracao_min": 30}
]
}
]
}Cada item deve incluir o campo que você escolheu como identificador ao ativar a conexão (neste exemplo, ref). Os demais campos são livres e são detectados automaticamente.
Dados aninhados
- Objetos aninhados são achatados com ponto:
horario.segunda_sexta - Arrays de texto são unidos:
"Odontologia, Ortopedia, Dermatologia" - Arrays de objetos são indexados como sub-registros, o que permite ao chatbot filtrar por sub-campos (ex.: "serviços de menos de 200 reais")
Excluir dados
Envie "action": "delete" com os identificadores dos registros a excluir. A exclusão inclui os sub-registros associados.
{
"action": "delete",
"items": [
{ "ref": "SP-01" }
]
}Excluir um registro inexistente não produz erro. A operação é idempotente.
Respostas
Sucesso (200)
{
"ok": true,
"request_id": "req_7a3f1b2c",
"action": "upsert",
"stats": {
"processed": 1,
"created": 1,
"updated": 0,
"skipped": 0,
"children_created": 3,
"children_deleted": 0,
"chunks_regenerated": 1
}
}skipped— o conteúdo do item não mudou (os embeddings não são regenerados)children_created/deleted— sub-registros de arrays de objetoschunks_regenerated— textos recalculados para a busca semântica
Erro (4xx/5xx)
{
"detail": "Invalid signature"
}O campo detail descreve o erro em inglês. Os códigos HTTP indicam a categoria (veja a tabela de erros abaixo).
Códigos de erro
| Código | Causa | O que fazer |
|---|---|---|
| 400 | JSON malformado, falta o campo identificador, ou estrutura inválida | Revise o payload. Não tente de novo sem corrigir. |
| 401 | A assinatura HMAC não coincide | Verifique se está usando o secret correto e assinando exatamente o body que envia. |
| 403 | Request enviado por HTTP em vez de HTTPS | Use sempre HTTPS. |
| 404 | O bot ou dataset não existe | Confira bot_id e dataset_slug no painel. |
| 409 | O dataset ainda não foi ativado | Complete a ativação no painel de administração. |
| 413 | O body passa de 5 MB | Divida o envio em lotes menores. |
| 429 | Outro webhook está sendo processado ou houve tentativas demais com falha | Espere 30 segundos e tente de novo. |
| 500 | Erro interno do servidor | Tente de novo com backoff exponencial (1s, 2s, 4s, 8s, máx. 5 tentativas). |
Limites
50 MB
Carga inicial (URL)
5 MB
Body por webhook
500
Itens por webhook
HTTPS
Obrigatório
Atualizações em massa: se você precisa atualizar mais de 500 registros via webhook, envie-os em lotes sequenciais de até 500 cada. A carga inicial por URL não tem esse limite.
Novas tentativas: diante de um 5xx, tente de novo com backoff exponencial. Diante de um 4xx, corrija o payload antes de tentar. Diante de um 429, espere 30 segundos.
Idempotência: enviar o mesmo upsert duas vezes não duplica dados — o segundo request atualiza o que já existe.
Exemplo completo
Um script completo que envia dados, assina com HMAC e trata a resposta.
import hmac, hashlib, json, requests
SECRET = "SEU_SECRET_AQUI"
ENDPOINT = "https://api.bravos-ai.com/integrations/custom-webhook/{BOT_ID}/{DATASET_SLUG}/sync"
clinicas = [
{
"ref": "SP-01",
"nome": "Clínica São Paulo Centro",
"endereco": "Av. Paulista, 1000",
"servicos": [
{"nome": "Limpeza dental", "preco": 250},
{"nome": "Consulta dermatológica", "preco": 350},
],
},
{
"ref": "RJ-01",
"nome": "Clínica Rio Copacabana",
"endereco": "Av. Atlântica, 500",
"servicos": [
{"nome": "Revisão geral", "preco": 200},
{"nome": "Fisioterapia", "preco": 180},
],
},
]
body = json.dumps({"action": "upsert", "items": clinicas}).encode()
signature = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
r = requests.post(ENDPOINT, data=body, headers={
"Content-Type": "application/json",
"X-Bravos-Signature": signature,
}, timeout=30)
data = r.json()
if data.get("ok"):
print(f"Processados: {data['stats']['processed']}")
print(f"Criados: {data['stats']['created']}, Atualizados: {data['stats']['updated']}")
else:
print(f"Erro {r.status_code}: {data.get('detail')}")