Pular para o conteúdo

Webhooks (saída)

Base path: /api/v1/webhooks Scope: webhooks:manage

Webhooks de saída são URLs que o CRM chama (POST) quando eventos internos acontecem — contact.created, deal.won, proposal.accepted, etc. Use pra push em real-time pra Zapier, Make, n8n, ou seu próprio backend.

Inverso dos endpoints REST (onde você puxa do CRM), webhooks deixam o CRM te avisar quando algo acontece — mais eficiente que polling.

Eventos disponíveis

GET /api/v1/webhooks/event-types

Scope: webhooks:manage

Retorna o catálogo de eventos que podem disparar webhooks. Útil pra montar UI de configuração.

[
{ "key": "contact.created", "label": "Novo contato criado", "objectType": "contact" },
{ "key": "contact.updated", "label": "Contato atualizado", "objectType": "contact" },
{ "key": "deal.created", "label": "Novo negócio criado", "objectType": "deal" },
{ "key": "deal.stage_changed", "label": "Negócio mudou de etapa", "objectType": "deal" },
{ "key": "deal.won", "label": "Negócio ganho", "objectType": "deal" },
{ "key": "deal.lost", "label": "Negócio perdido", "objectType": "deal" },
{ "key": "proposal.sent", "label": "Proposta enviada", "objectType": "proposal" },
{ "key": "proposal.accepted", "label": "Proposta aceita", "objectType": "proposal" }
]

Lista webhooks

GET /api/v1/webhooks

Scope: webhooks:manage

[
{
"id": "ckl4w01...",
"name": "Slack — novos deals",
"url": "https://hooks.slack.com/services/T.../B.../...",
"events": ["deal.created", "deal.won"],
"isActive": true,
"secret": "whsec_<sha-prefix>...",
"createdAt": "2026-03-01T00:00:00.000Z"
}
]

O campo secret retornado é só o prefixo — o segredo cru completo é mostrado uma única vez na criação (igual API Keys).

Cria um webhook

POST /api/v1/webhooks

Scope: webhooks:manage

CampoTipoObrigatórioDescrição
namestringNome (referência humana)
urlstringURL HTTPS de destino
eventsstring[]Array de event keys (ver /event-types)
isActivebooleanDefault true
headersobjectHeaders customizados (ex: API key do destino)
Terminal window
curl -X POST https://app.indutivacrm.com.br/api/v1/webhooks \
-H "X-API-Key: crm_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Slack — alertas vendas",
"url": "https://hooks.slack.com/services/T.../B.../...",
"events": ["deal.won", "deal.lost"]
}'

A resposta inclui secret (string completa, começa com whsec_) — copie agora, não dá pra recuperar depois.

Atualiza um webhook

PATCH /api/v1/webhooks/:id

Scope: webhooks:manage

Terminal window
# Desativar temporariamente
curl -X PATCH https://app.indutivacrm.com.br/api/v1/webhooks/ckl4w01... \
-H "X-API-Key: crm_live_..." \
-H "Content-Type: application/json" \
-d '{"isActive": false}'

Deleta um webhook

DELETE /api/v1/webhooks/:id

Scope: webhooks:manage

Resposta 204 No Content. O webhook para de disparar imediatamente.

Testa um webhook

POST /api/v1/webhooks/:id/test

Scope: webhooks:manage

Dispara um payload de exemplo (event: webhook.test) pra URL configurada. Útil pra verificar se a URL responde corretamente antes de eventos reais começarem.

Lista deliveries

GET /api/v1/webhooks/:id/deliveries?limit=50

Scope: webhooks:manage

Histórico de tentativas de envio (sucesso ou falha) pra esse webhook. Útil pra debug.

[
{
"id": "ckl4d01...",
"event": "deal.won",
"statusCode": 200,
"responseSnippet": "{\"ok\":true}",
"durationMs": 234,
"attempts": 1,
"deliveredAt": "2026-05-22T14:30:00.000Z"
},
{
"id": "ckl4d02...",
"event": "deal.lost",
"statusCode": 500,
"responseSnippet": "Internal Server Error",
"durationMs": 5012,
"attempts": 3,
"lastError": "Server returned 500",
"deliveredAt": "2026-05-22T13:00:00.000Z"
}
]

Shape do payload entregue

Quando um evento dispara, o CRM faz um POST pra sua URL com:

POST https://sua-url.com/webhook
Content-Type: application/json
X-Indutiva-Event: deal.won
X-Indutiva-Signature: sha256=<hmac>
X-Indutiva-Delivery: <uuid>
User-Agent: IndutivaCRM-Webhook/1.0
{
"event": "deal.won",
"tenantId": "ckl4t01...",
"timestamp": "2026-05-22T14:30:00.000Z",
"data": {
"deal": {
"id": "ckl4d01...",
"title": "Proposta Acme",
"value": 24000,
...
}
}
}

Verificando a assinatura HMAC

Toda entrega inclui o header X-Indutiva-Signature: sha256=<hmac> — HMAC SHA-256 do body raw usando seu secret como chave.

Node.js:

import crypto from 'crypto'
function verifyWebhook(rawBody, signature, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
// Comparação timing-safe (importante!)
const a = Buffer.from(signature)
const b = Buffer.from(expected)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
// Express
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.header('X-Indutiva-Signature')
if (!verifyWebhook(req.body, sig, process.env.CRM_WEBHOOK_SECRET)) {
return res.status(401).send('invalid signature')
}
const payload = JSON.parse(req.body.toString())
// ... processa payload
res.json({ ok: true })
})

Retries e idempotência

  • Se sua URL retorna 2xx, é considerada entrega bem-sucedida
  • Se retorna 4xx ou 5xx, ou timeout (>10s), o CRM retenta com backoff exponencial: 1min, 5min, 30min, 2h, 12h (máx 5 tentativas)
  • Após 5 falhas, a entrega é marcada permanent_failure e desiste

Seu handler precisa ser idempotente — pode receber o mesmo evento múltiplas vezes (rede flaky, retries). Use X-Indutiva-Delivery (UUID único por tentativa de entrega) ou um campo do payload (ex: deal.id + event) pra deduplicar.

Erros específicos

StatuserrorCenário
400invalid_eventAlgum item de events não está no catálogo
400invalid_urlURL não é HTTPS ou malformada
404not_foundWebhook não existe