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.
| Evento | Origem | Descrição |
|---|---|---|
sale.dispatch | Job interno / POST /api/sales/{id}/trigger-webhook | Envia o payload completo de uma venda para SALES_WEBHOOK_URL. |
webhook_received | PATCH /api/sales/{id}/status | Confirma que o consumidor recebeu o payload. Marca webhookProcessed = true. |
template_sent | PATCH /api/sales/{id}/status | Template de mensagem enviado ao lead. |
contact_failed | PATCH /api/sales/{id}/status | Tentativa de contato falhou; aceita no_contact_reason. |
lead_forwarded | PATCH /api/sales/{id}/status | Lead 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.0Como 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-AgentDigibrokerAPI/1.0em 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
...