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-typesScope: 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/webhooksScope: 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/webhooksScope: webhooks:manage
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
name | string | ✅ | Nome (referência humana) |
url | string | ✅ | URL HTTPS de destino |
events | string[] | ✅ | Array de event keys (ver /event-types) |
isActive | boolean | — | Default true |
headers | object | — | Headers customizados (ex: API key do destino) |
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/:idScope: webhooks:manage
# Desativar temporariamentecurl -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/:idScope: webhooks:manage
Resposta 204 No Content. O webhook para de disparar imediatamente.
Testa um webhook
POST /api/v1/webhooks/:id/testScope: 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=50Scope: 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/webhookContent-Type: application/jsonX-Indutiva-Event: deal.wonX-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)}
// Expressapp.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
4xxou5xx, 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_failuree 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
| Status | error | Cenário |
|---|---|---|
| 400 | invalid_event | Algum item de events não está no catálogo |
| 400 | invalid_url | URL não é HTTPS ou malformada |
| 404 | not_found | Webhook não existe |