API do LaurinhIA — v2.0

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.

Base URL
https://laurinhia.com/api/v2
MétodoEndpointDescrição
GET/pacientesListar pacientes (paginado, filtro por status)
POST/pacientesCriar novo paciente
GET/pacientes/{id}Buscar paciente por ID
PATCH/pacientes/{id}Atualizar paciente
DELETE/pacientes/{id}Anonimizar (LGPD soft-delete)
GET/agendamentosListar agendamentos (filtro por data/status)
POST/agendamentosCriar 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.

Exemplo cURL
curl -X GET "https://laurinhia.com/api/v2/pacientes" \
  -H "X-API-Key: sk_live_sua_chave_aqui"
CódigoSignificado
401 MISSING_API_KEYHeader X-API-Key ausente
401 INVALID_API_KEYKey inválida ou não encontrada
401 API_KEY_REVOKEDKey foi revogada
401 API_KEY_EXPIREDKey expirada
403 INSUFFICIENT_PERMISSIONSKey não tem a permissão para esta operação
429 RATE_LIMIT_EXCEEDEDMais de 100 req/min com a mesma key

Permissões disponíveis

pacientes:read

GET em /pacientes e /pacientes/{id}

pacientes:write

POST, PATCH, DELETE em /pacientes

agendamentos:read

GET em /agendamentos

agendamentos:write

POST, PATCH, DELETE em /agendamentos

Pacientes

GET/api/v2/pacientes

Parâmetros de query opcionais:

  • status — captacao | agendado | em_tratamento | finalizado | opt_out
  • page — 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 } }
POST/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
}
LGPD: Campos de saúde nunca são retornados. O DELETE executa anonimização (LGPD Art. 18, VI) — mantém o registro para integridade referencial, substituindo dados pessoais por valores anônimos.

Agendamentos

GET/api/v2/agendamentos

Parâmetros de query opcionais:

  • status — pendente | confirmado | cancelado | realizado | no_show | reagendado
  • data_inicio — ISO 8601 (ex: 2026-04-01T00:00:00Z)
  • data_fim — ISO 8601
  • paciente_id — UUID do paciente
  • dentista_id — UUID do dentista
  • page / limit — paginação
POST/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
}
Exemplo: Buscar agendamentos de amanhã
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. 1

    Acesse Configurações → Webhooks

    No painel do LaurinhIA, vá em Configurações e clique na aba "Webhooks de Saída".

  2. 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. 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. 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. 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

HeaderDescrição
Content-Type
application/json
Sempre JSON.
X-Webhook-Signature
sha256=<hex>
Assinatura HMAC-SHA256 do body. Verifique antes de processar.
X-Webhook-Event
lead.created
Nome do evento que gerou esta entrega.
X-Webhook-Timestamp
2026-03-25T14:00:00.000Z
Timestamp ISO-8601 da entrega.
User-Agent
SaaS-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:

Imediato
+5 segundos
+30 segundos
Falha
Registrado nos logs

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.created

Disparado 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.updated

Disparado 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.booked

Disparado 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.canceled

Disparado 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.sent

Disparado 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.received

Disparado 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.

Node.js
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);
}
Python
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
<?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
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"]
  }'
Resposta (201 Created)
{
  "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.