simpla.fydocs
Seguros API

Webhooks

Eventos de vendas emitidos pelo Simplafy Seguros para sistemas externos: payloads, entrega, retry e exemplo de handler.

Webhooks

O Simplafy Seguros emite webhooks de saída (outbound) para notificar sistemas externos sobre eventos de vendas (Sell). O serviço age como produtor: monta o payload, envia via POST para a URL configurada e marca o registro como processado.

A URL de destino é definida pela variável de ambiente SALES_WEBHOOK_URL. Não há painel para múltiplos endpoints — existe um único destino por ambiente.

O Seguros API não expõe um endpoint para receber webhooks de terceiros. Para reagir a callbacks externos (status de contato, encaminhamento de lead), use o endpoint de status PATCH /api/sales/{id}/status documentado em Sales.

Tipos de evento

O ciclo de vida de uma venda gera os eventos abaixo. Eventos de status são confirmados por PATCH /api/sales/{id}/status com o campo event; o evento de webhook propriamente dito é o disparo automático após criação ou aprovação de financiamento.

EventoOrigemDescrição
sale.dispatchJob interno / POST /api/sales/{id}/trigger-webhookEnvia o payload completo de uma venda para SALES_WEBHOOK_URL.
webhook_receivedPATCH /api/sales/{id}/statusConfirma que o consumidor recebeu o payload. Marca webhookProcessed = true.
template_sentPATCH /api/sales/{id}/statusTemplate de mensagem enviado ao lead.
contact_failedPATCH /api/sales/{id}/statusTentativa de contato falhou; aceita no_contact_reason.
lead_forwardedPATCH /api/sales/{id}/statusLead encaminhado para concessionária; aceita sent_to, sent_to_number, dealership.

Payload

O payload é enviado como um array de vendas, permitindo entrega em lote. O job de envio agrupa vendas pendentes; o disparo manual envia uma única venda com totalVendas: 1.

Estrutura raiz:

{
  "vendas": [
    {
      "venda": { "...": "campos da venda" },
      "pessoa": { "...": "dados do cliente" },
      "endereco": { "...": "endereço ou null" },
      "veiculo": { "...": "dados do veículo ou null" },
      "vendedor": { "...": "vendedor responsável ou null" },
      "timestamp": "2026-05-29T12:34:56.000Z"
    }
  ],
  "totalVendas": 1,
  "timestamp": "2026-05-29T12:34:56.000Z"
}

Exemplo completo de uma entrada de vendas[]:

{
  "venda": {
    "id": "9b7b3f6e-3b6c-4b7e-9b3a-1234567890ab",
    "sellerId": "V001",
    "sellerName": "João Silva",
    "financingApprovalDate": "2026-05-28T10:00:00.000Z",
    "createdAt": "2026-05-28T09:55:12.000Z",
    "updatedAt": "2026-05-29T12:34:56.000Z"
  },
  "pessoa": {
    "id": "f1c2b3a4-5d6e-7f8a-9b0c-112233445566",
    "fullName": "Maria Souza",
    "cpf": "12345678900",
    "rg": "MG-12.345.678",
    "cnh": "12345678901",
    "birthDate": "1985-04-12",
    "gender": "F",
    "maritalStatus": "casado",
    "personalEmail": "[email protected]",
    "commercialEmail": null,
    "residentialPhone": "6133334444",
    "commercialPhone": null,
    "mobile": "61999998888",
    "optionalPhone": null,
    "createdAt": "2026-05-28T09:55:12.000Z",
    "updatedAt": "2026-05-28T09:55:12.000Z"
  },
  "endereco": {
    "street": "SQN 123",
    "number": "10",
    "city": "Brasília",
    "state": "DF",
    "neighborhood": "Asa Norte",
    "complement": "Apto 201",
    "zipCode": "70000000"
  },
  "veiculo": {
    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "manufacturer": "Toyota",
    "brand": "Toyota",
    "model": "Corolla",
    "color": "Prata",
    "manufactureYear": 2025,
    "modelYear": 2026,
    "fipeCode": "002001-1",
    "renavan": "12345678901",
    "chassi": "9BWZZZ377VT004251",
    "fuelType": "flex"
  },
  "vendedor": {
    "id": "11111111-2222-3333-4444-555555555555",
    "vendorId": "V001",
    "name": "João Silva",
    "isActive": true,
    "webhookEnabled": true
  },
  "timestamp": "2026-05-29T12:34:56.000Z"
}

Campos endereco, veiculo e vendedor podem ser null quando a venda não tem o relacionamento preenchido.

Assinatura HMAC

Atualmente o Seguros API não assina os payloads emitidos. Os headers fixos do disparo são:

POST <SALES_WEBHOOK_URL>
Content-Type: application/json
User-Agent: DigibrokerAPI/1.0

Como não há assinatura HMAC nativa, recomenda-se proteger o endpoint consumidor por uma das opções abaixo:

  • Restringir o destino por IP allowlist no ingress/firewall.
  • Exigir um token estático em header customizado (ex.: X-Webhook-Token) injetado por um proxy.
  • Validar o User-Agent DigibrokerAPI/1.0 em conjunto com outra camada.

Suporte a assinatura HMAC de payload será adicionado em versão futura.

Retry

O modelo de entrega é baseado em flag de processamento no registro Sell, não em uma fila com backoff exponencial.

Envio inicial. O job interno ou a chamada manual a POST /api/sales/{id}/trigger-webhook faz POST para SALES_WEBHOOK_URL com timeout de 30 segundos.

Sucesso. Resposta HTTP 2xx marca webhookProcessed = true e forceReprocess = false. A venda não é mais enviada.

Falha. Em qualquer erro de transporte ou HTTP não-2xx, webhookProcessed permanece false. O job periódico tentará novamente nas próximas execuções enquanto a venda estiver dentro da janela elegível.

Reprocessamento manual. POST /api/sales/{id}/trigger-webhook força o reenvio mesmo para vendas já processadas ou fora da janela padrão, marcando forceReprocess = true e zerando webhookProcessed.

Confirmação explícita do consumidor (idempotência do lado do receptor):

curl -X PATCH "https://api-seguros.simplafy.com.br/api/sales/{id}/status" \
  -H "x-api-key: $SIMPLAFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"webhook_received"}'

Não há header de tentativa (X-Retry-Count) nem ID único de entrega por chamada. A idempotência deve ser garantida pelo consumidor usando vendas[].venda.id como chave.

Exemplo de handler

Handler mínimo em Node.js/Express que valida o payload, persiste o lote de forma idempotente e responde 200 rapidamente para liberar o produtor.

import express from 'express';

type VendaEnvelope = {
  vendas: Array<{
    venda: { id: string; sellerId: string; sellerName: string };
    pessoa: { id: string; fullName: string; cpf: string; mobile: string };
    endereco: Record<string, string> | null;
    veiculo: Record<string, unknown> | null;
    vendedor: { id: string; vendorId: string; name: string } | null;
    timestamp: string;
  }>;
  totalVendas: number;
  timestamp: string;
};

const app = express();
app.use(express.json({ limit: '1mb' }));

app.post('/webhooks/simplafy-seguros', async (req, res) => {
  const userAgent = req.header('user-agent') ?? '';
  if (!userAgent.startsWith('DigibrokerAPI/')) {
    return res.status(401).json({ error: 'unexpected user-agent' });
  }

  const body = req.body as VendaEnvelope;
  if (!Array.isArray(body?.vendas) || body.vendas.length === 0) {
    return res.status(400).json({ error: 'invalid payload' });
  }

  // Responde rápido — processa em background para evitar timeout de 30s.
  res.status(200).json({ received: body.totalVendas });

  for (const item of body.vendas) {
    try {
      await upsertSale(item.venda.id, item);
    } catch (err) {
      console.error('falha ao processar venda', item.venda.id, err);
    }
  }
});

async function upsertSale(saleId: string, payload: unknown) {
  // Persistir por saleId garante idempotência se a Simplafy reenviar.
  // ex.: db.sales.upsert({ where: { id: saleId }, create: ..., update: ... })
}

app.listen(3000);

Em Python/FastAPI o padrão é equivalente: validar com Pydantic, devolver 200 rápido e processar de forma assíncrona.

from fastapi import FastAPI, Header, HTTPException, BackgroundTasks
from pydantic import BaseModel
from typing import Any

app = FastAPI()

class Venda(BaseModel):
    venda: dict[str, Any]
    pessoa: dict[str, Any]
    endereco: dict[str, Any] | None = None
    veiculo: dict[str, Any] | None = None
    vendedor: dict[str, Any] | None = None
    timestamp: str

class Envelope(BaseModel):
    vendas: list[Venda]
    totalVendas: int
    timestamp: str

@app.post("/webhooks/simplafy-seguros")
async def receive(
    payload: Envelope,
    background: BackgroundTasks,
    user_agent: str = Header(default=""),
):
    if not user_agent.startswith("DigibrokerAPI/"):
        raise HTTPException(status_code=401, detail="unexpected user-agent")

    for item in payload.vendas:
        background.add_task(persist_sale, item.venda["id"], item.model_dump())

    return {"received": payload.totalVendas}

async def persist_sale(sale_id: str, payload: dict[str, Any]) -> None:
    # upsert por sale_id para garantir idempotência
    ...

On this page