Developer Docs
Integre seu sistema ao CRM Odonto via REST API v2 (CRUD completo) ou receba eventos em tempo real via Webhooks de Saída com HMAC-SHA256.
REST API v2
CRUD completo para pacientes e agendamentos. Ideal para sincronizar seu sistema ERP, criar automações via Zapier/Make, ou integrar com sistemas próprios. Requer plano Pro ou Enterprise.
https://laurinhia.com/api/v2
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /pacientes | Listar pacientes (paginado, filtro por status) |
| POST | /pacientes | Criar novo paciente |
| GET | /pacientes/{id} | Buscar paciente por ID |
| PATCH | /pacientes/{id} | Atualizar paciente |
| DELETE | /pacientes/{id} | Anonimizar (LGPD soft-delete) |
| GET | /agendamentos | Listar agendamentos (filtro por data/status) |
| POST | /agendamentos | Criar novo agendamento |
| GET | /agendamentos/{id} | Buscar agendamento por ID |
| PATCH | /agendamentos/{id} | Atualizar agendamento |
| DELETE | /agendamentos/{id} | Cancelar agendamento |
Autenticação com API Key
Todas as rotas /api/v2/* requerem uma API Key no header X-API-Key. As keys são gerenciadas em Configurações → REST API.
curl -X GET "https://laurinhia.com/api/v2/pacientes" \ -H "X-API-Key: sk_live_sua_chave_aqui"
| Código | Significado |
|---|---|
401 MISSING_API_KEY | Header X-API-Key ausente |
401 INVALID_API_KEY | Key inválida ou não encontrada |
401 API_KEY_REVOKED | Key foi revogada |
401 API_KEY_EXPIRED | Key expirada |
403 INSUFFICIENT_PERMISSIONS | Key não tem a permissão para esta operação |
429 RATE_LIMIT_EXCEEDED | Mais de 100 req/min com a mesma key |
Permissões disponíveis
pacientes:readGET em /pacientes e /pacientes/{id}
pacientes:writePOST, PATCH, DELETE em /pacientes
agendamentos:readGET em /agendamentos
agendamentos:writePOST, PATCH, DELETE em /agendamentos
Pacientes
/api/v2/pacientesParâmetros de query opcionais:
status— captacao | agendado | em_tratamento | finalizado | opt_outpage— página (default: 1)limit— itens por página (default: 20, max: 100)
Resposta 200
{ "data": [...], "meta": { "page": 1, "limit": 20, "total": 47, "total_pages": 3 } }/api/v2/pacientes{
"telefone": "5573999990001", // obrigatório
"nome": "Maria Silva", // opcional
"canal_origem": "formulario_web", // opcional
"consentimento_lgpd": true, // obrigatório por LGPD
"consentimento_whatsapp": true // opcional
}Agendamentos
/api/v2/agendamentosParâmetros de query opcionais:
status— pendente | confirmado | cancelado | realizado | no_show | reagendadodata_inicio— ISO 8601 (ex: 2026-04-01T00:00:00Z)data_fim— ISO 8601paciente_id— UUID do pacientedentista_id— UUID do dentistapage/limit— paginação
/api/v2/agendamentos{
"data_agendamento": "2026-04-15T09:00:00-03:00", // obrigatório (ISO 8601 com timezone)
"paciente_id": "uuid-do-paciente", // opcional
"dentista_id": "uuid-do-dentista", // opcional
"data_hora_fim": "2026-04-15T10:00:00-03:00", // opcional
"procedimento": "Limpeza", // opcional
"observacoes": "Paciente com histórico de ansiedade" // opcional
}curl "https://laurinhia.com/api/v2/agendamentos?data_inicio=2026-04-15T00:00:00Z&data_fim=2026-04-15T23:59:59Z&status=confirmado" \ -H "X-API-Key: sk_live_sua_chave_aqui"
Webhooks de Saída
Como começar
- 1
Acesse Configurações → Webhooks
No painel do LaurinhIA, vá em Configurações e clique na aba "Webhooks de Saída".
- 2
Crie um webhook
Informe a URL do seu endpoint HTTPS, escolha os eventos que deseja receber e clique em Criar. Um secret HMAC-SHA256 será gerado automaticamente.
- 3
Salve o secret
Copie o secret imediatamente após a criação — ele é exibido apenas uma vez. Armazene-o como variável de ambiente no seu servidor.
- 4
Verifique a assinatura
A cada requisição recebida, verifique o header X-Webhook-Signature usando o secret salvo. Rejeite requisições sem assinatura válida.
- 5
Teste o webhook
Use o botão "Testar" no painel para enviar um evento de teste e confirmar que o endpoint responde com HTTP 2xx.
Headers da Requisição
| Header | Descrição |
|---|---|
Content-Typeapplication/json | Sempre JSON. |
X-Webhook-Signaturesha256=<hex> | Assinatura HMAC-SHA256 do body. Verifique antes de processar. |
X-Webhook-Eventlead.created | Nome do evento que gerou esta entrega. |
X-Webhook-Timestamp2026-03-25T14:00:00.000Z | Timestamp ISO-8601 da entrega. |
User-AgentSaaS-Odonto-Webhook/1.0 | Identificação do sistema de origem. |
Política de Retry
Se o seu endpoint retornar um status HTTP fora do range 2xx, ou não responder em até 10 segundos, a entrega é marcada como falha. O sistema tenta novamente com backoff exponencial:
Todas as tentativas são registradas em Configurações → Webhooks → Logs de entrega.
Eventos
Cada webhook pode assinar um ou mais eventos abaixo. O campo event no payload sempre identifica o tipo.
lead.createdDisparado quando um novo paciente é captado (via WhatsApp, formulário ou import).
LGPD: Nome e telefone não incluídos por padrão (LGPD). Use o paciente_id para consultar via API.
Payload de exemplo
{
"event": "lead.created",
"timestamp": "2026-03-25T14:00:00.000Z",
"data": {
"paciente_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "captacao",
"canal_origem": "whatsapp",
"criado_em": "2026-03-25T14:00:00.000Z"
}
}patient.updatedDisparado quando o status de um paciente é alterado no Kanban.
LGPD: Apenas IDs e status são incluídos no payload.
Payload de exemplo
{
"event": "patient.updated",
"timestamp": "2026-03-25T14:05:00.000Z",
"data": {
"paciente_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status_anterior": "captacao",
"status_novo": "agendado",
"atualizado_em": "2026-03-25T14:05:00.000Z"
}
}appointment.bookedDisparado quando um agendamento é criado ou confirmado.
LGPD: Procedimento pode ser omitido se a clínica configurar privacidade máxima.
Payload de exemplo
{
"event": "appointment.booked",
"timestamp": "2026-03-25T14:10:00.000Z",
"data": {
"agendamento_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"paciente_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data_agendamento": "2026-03-28T09:00:00.000Z",
"procedimento": "Limpeza",
"status": "pendente",
"criado_em": "2026-03-25T14:10:00.000Z"
}
}appointment.canceledDisparado quando um agendamento é cancelado ou marcado como no-show.
Payload de exemplo
{
"event": "appointment.canceled",
"timestamp": "2026-03-25T14:15:00.000Z",
"data": {
"agendamento_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"paciente_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status_anterior": "confirmado",
"status_novo": "cancelado",
"atualizado_em": "2026-03-25T14:15:00.000Z"
}
}campaign.sentDisparado quando uma campanha de recuperação ou repescagem é enviada.
LGPD: IDs de pacientes individuais nunca incluídos no payload da campanha.
Payload de exemplo
{
"event": "campaign.sent",
"timestamp": "2026-03-25T14:20:00.000Z",
"data": {
"campanha_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"tipo": "repescagem",
"total_enviados": 47,
"criado_em": "2026-03-25T14:20:00.000Z"
}
}nps.receivedDisparado quando um paciente responde à pesquisa NPS pós-consulta.
LGPD: Comentário do paciente não incluído por padrão (LGPD).
Payload de exemplo
{
"event": "nps.received",
"timestamp": "2026-03-25T14:25:00.000Z",
"data": {
"paciente_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"nota": 9,
"respondido_em": "2026-03-25T14:25:00.000Z"
}
}Verificando a Assinatura (HMAC-SHA256)
Sempre verifique o header X-Webhook-Signature antes de processar o payload. A assinatura é calculada como sha256=HMAC(secret, body_bytes). Use comparação segura (timing-safe) para evitar ataques de timing.
const crypto = require("crypto");
function verificarWebhook(req) {
const assinaturaRecebida = req.headers["x-webhook-signature"];
const payload = JSON.stringify(req.body);
const assinaturaEsperada = "sha256=" + crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(payload, "utf8")
.digest("hex");
// Comparação segura contra timing attacks
const recvBuf = Buffer.from(assinaturaRecebida ?? "", "ascii");
const expBuf = Buffer.from(assinaturaEsperada, "ascii");
if (recvBuf.length !== expBuf.length) return false;
return crypto.timingSafeEqual(recvBuf, expBuf);
}import hmac, hashlib
def verificar_webhook(body_bytes: bytes, assinatura_recebida: str, secret: str) -> bool:
"""Verifica assinatura HMAC-SHA256 do webhook."""
assinatura_esperada = "sha256=" + hmac.new(
secret.encode("utf-8"),
body_bytes,
hashlib.sha256
).hexdigest()
# Comparação segura contra timing attacks
return hmac.compare_digest(assinatura_recebida, assinatura_esperada)
# Em um handler FastAPI:
from fastapi import Request, HTTPException
@app.post("/meu-webhook")
async def handle_webhook(request: Request):
body = await request.body()
sig = request.headers.get("x-webhook-signature", "")
if not verificar_webhook(body, sig, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Assinatura inválida")
payload = await request.json()
# processar...<?php
function verificarWebhook(string $payload, string $assinaturaRecebida, string $secret): bool {
$assinaturaEsperada = "sha256=" . hash_hmac("sha256", $payload, $secret);
// Comparação segura contra timing attacks
return hash_equals($assinaturaEsperada, $assinaturaRecebida);
}
// Em um controller Laravel / endpoint puro:
$payload = file_get_contents("php://input");
$assinatura = $_SERVER["HTTP_X_WEBHOOK_SIGNATURE"] ?? "";
if (!verificarWebhook($payload, $assinatura, getenv("WEBHOOK_SECRET"))) {
http_response_code(401);
die("Assinatura inválida");
}
$dados = json_decode($payload, true);
// processar...Exemplo: Criar webhook via API
Você também pode criar webhooks programaticamente via a API REST do LaurinhIA. Requer autenticação com um token de sessão Supabase.
curl -X POST "https://laurinhia.com/api/webhooks/outgoing" \
-H "Authorization: Bearer <SEU_SUPABASE_JWT>" \
-H "Content-Type: application/json" \
-d '{
"nome": "Meu Sistema",
"url": "https://meusite.com/webhook/odonto",
"eventos": ["lead.created", "appointment.booked", "nps.received"]
}'{
"success": true,
"data": {
"id": "d4e5f6a7-b8c9-0123-def0-123456789012",
"nome": "Meu Sistema",
"url": "https://meusite.com/webhook/odonto",
"secret": "a3b4c5d6e7f8...64hex...chars",
"eventos": ["lead.created", "appointment.booked", "nps.received"],
"ativo": true,
"criado_em": "2026-03-25T14:00:00.000Z"
}
}Salve o campo secret imediatamente — ele não será retornado nas consultas futuras por segurança.
Boas Práticas
- ✓Responda com HTTP 200 o mais rápido possível. Processe o evento em background para evitar timeout.
- ✓Implemente idempotência: o mesmo evento pode ser entregue mais de uma vez em caso de falha de rede. Use o campo id do objeto data para deduplicar.
- ✓Valide sempre a assinatura HMAC antes de processar. Rejeite com 401 se inválida.
- ✓Armazene o secret como variável de ambiente — nunca no código-fonte.
- ✓Monitore os logs de entrega no painel para identificar falhas rapidamente.
- ✓Use HTTPS com certificado válido. Endpoints HTTP não são aceitos.