Webhooks de saída são o mecanismo reverso da API: ao invés de você chamar o Syncro pra buscar dados, o Syncro chama você quando algo acontece. Útil pra integração com n8n, Zapier custom, sistemas legados ou qualquer endpoint HTTP. Esse artigo cobre como criar subscription, eventos disponíveis, retry e segurança.
Pré-requisitos
- API Key gerada. Veja Gerar API Key.
- Endpoint HTTP público que recebe POST JSON.
- HTTPS recomendado (não obrigatório).
Como funciona
1. Você cadastra subscription com URL + lista de eventos.
2. Algo acontece no Syncro (ex: lead criado).
3. Sistema dispara job assíncrono.
4. Job faz POST pro seu endpoint com payload + assinatura HMAC.
5. Seu sistema responde 2xx (sucesso) ou erro.
6. Erros disparam retry com backoff.
Criar subscription
Endpoint
POST https://app.syncro.chat/api/v1/webhooks
Payload
{
"target_url": "https://meusistema.com/webhook/syncro",
"events": [
"lead.created",
"lead.updated",
"lead.won"
],
"source": "api",
"secret": "meu_secret_opcional"
}
| Campo | Obrigatório | Descrição |
|---|---|---|
target_url |
✅ | URL completa que recebe POST |
events |
✅ | Array de eventos a inscrever |
source |
❌ | api (default) ou n8n |
secret |
❌ | Chave pra assinar HMAC. Se vazio, sistema gera 40 chars |
Resposta (201)
{
"success": true,
"id": 42,
"secret": "abc123def456...",
"target_url": "https://meusistema.com/webhook/syncro",
"events": ["lead.created", "lead.updated", "lead.won"],
"is_active": true,
"created_at": "2026-04-30T14:30:00Z"
}
⚠️ Atenção:
secretaparece apenas na resposta da criação. Salve agora — é necessário pra validar assinatura HMAC.
Eventos disponíveis
| Evento | Quando dispara |
|---|---|
lead.created |
Lead novo criado (manual, API, form, webhook) |
lead.updated |
Qualquer campo do lead muda |
lead.stage_changed |
Lead muda de etapa |
lead.won |
Lead vai pra etapa won |
lead.lost |
Lead vai pra etapa lost |
lead.deleted |
Lead deletado |
task.created |
Task nova criada |
task.updated |
Task atualizada |
task.completed |
Task marcada como completa |
conversation.message_received |
Nova mensagem inbound (todos canais) |
💡 Dica: você pode inscrever em múltiplos eventos numa mesma subscription. Sistema filtra por evento ao disparar.
Payload do webhook
Sistema envia POST com:
Headers
Content-Type: application/json
User-Agent: Syncro-CRM-Webhook/1.0
X-Syncro-Event: lead.created
X-Syncro-Delivery: unique_delivery_id
X-Syncro-Signature: sha256=<hmac>
Body JSON
{
"event": "lead.created",
"tenant_id": 123,
"emitted_at": "2026-04-30T14:30:00Z",
"data": {
"id": 1234,
"name": "João Silva",
"phone": "+5511987654321",
"email": "[email protected]",
"company": "Acme Corp",
"value": 5000,
"source": "site-form",
"status": "active",
"pipeline_id": 1,
"stage_id": 5,
"assigned_to": 3,
"tags": ["prospect"],
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "summer_2026",
"created_at": "2026-04-30T14:30:00Z",
"updated_at": "2026-04-30T14:30:00Z"
}
}
Eventos com contexto extra
lead.stage_changed:
{
"event": "lead.stage_changed",
"data": {
"id": 1234,
"name": "João Silva",
"previous_stage_id": 5,
"new_stage_id": 7,
"new_stage_name": "Negociação",
...
}
}
lead.won / lead.lost:
{
"event": "lead.won",
"data": {
...
"value": 10000,
"won_at": "2026-04-30T15:00:00Z"
}
}
Como o seu endpoint deve responder
Sucesso (2xx)
Qualquer status 2xx é considerado sucesso. Sistema marca:
failure_count = 0.last_success_at = now.
Mais comum: 200 OK com body vazio ou {"received": true}.
Erro 4xx (exceto 408/429)
Sistema considera erro permanente do receptor (request mal formada, endpoint não existe, etc):
- Marca
failure_count++. - NÃO retenta (é assumido que problema é do seu lado).
Erro 5xx, 408, 429, timeout
Sistema considera erro transitório:
- Marca failure.
- Retenta com backoff exponencial.
Retry exponencial
Se webhook falha, sistema retenta:
| Tentativa | Delay desde a anterior |
|---|---|
| 1ª | imediato |
| 2ª | 10 segundos |
| 3ª | 1 minuto |
| 4ª | 5 minutos |
| 5ª | 15 minutos |
Total: ~16 minutos pra desistir.
Auto-desativação
Se subscription falha 10+ vezes consecutivas:
- Sistema marca
is_active = false. - Para de tentar.
- Aparece como Desativada na listagem.
Pra reativar:
PUT /api/v1/webhooks/{id}
{
"is_active": true
}
Sistema reseta failure_count = 0.
SSRF protection
Sistema bloqueia webhooks pra:
- IPs privados:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16. - Loopback:
127.0.0.0/8. - Link-local:
169.254.0.0/16(incluindo169.254.169.254— AWS metadata). - Multicast e IPv6 ULA.
Tentativas bloqueadas marcam falha com last_failure_reason: "ssrf_blocked". Não fazem POST.
💡 Dica: pra desenvolvimento local, use serviço tipo ngrok ou webhook.site pra ter URL público.
Listar subscriptions
curl "https://app.syncro.chat/api/v1/webhooks" \
-H "X-API-Key: crm_..."
Resposta inclui métricas de saúde:
{
"success": true,
"data": [
{
"id": 42,
"target_url": "https://meusistema.com/webhook/syncro",
"events": ["lead.created"],
"is_active": true,
"failure_count": 0,
"last_success_at": "2026-04-30T14:30:00Z",
"last_failure_at": null,
"last_failure_reason": null
}
]
}
💡 Dica: monitore
failure_countperiodicamente. Spike = problema em produção.
Atualizar subscription
PUT /api/v1/webhooks/42
{
"events": ["lead.created", "lead.updated", "lead.won", "lead.lost"],
"is_active": true
}
Deletar subscription
DELETE /api/v1/webhooks/42
Imediato e irreversível. Subscription para de receber eventos.
Idempotência
Mesmo evento pode chegar 2x em casos raros:
- Bug em retry.
- Network split.
- Sistema processou request mas sua resposta não chegou.
Como lidar
Use header X-Syncro-Delivery (único por tentativa) como dedup key:
// Pseudo-código
$deliveryId = $_SERVER['HTTP_X_SYNCRO_DELIVERY'];
// Se já processou esse delivery_id, retorna 200 sem processar de novo
if (cache_has("processed:$deliveryId")) {
return response->json(['received' => true]);
}
// Processa
process_webhook($payload);
// Marca como processado por 24h
cache_put("processed:$deliveryId", true, 86400);
Múltiplas subscriptions pro mesmo evento
Sim, suportado. Cada uma recebe POST.
Útil pra:
- Backup (se uma falha, outra recebe).
- Splits (n8n recebe pra automação + sistema legado recebe pra log).
Exemplo — receptor em PHP
<?php
header('Content-Type: application/json');
// 1. Valida assinatura HMAC
$secret = 'seu_secret_aqui';
$signature = $_SERVER['HTTP_X_SYNCRO_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$expected = 'sha256='. hash_hmac('sha256', $body, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// 2. Parse payload
$payload = json_decode($body, true);
$event = $payload['event'];
$data = $payload['data'];
// 3. Processa
switch ($event) {
case 'lead.created':
my_db_create_lead($data);
break;
case 'lead.won':
my_email_send_thank_you($data);
break;
//...
}
// 4. Responde sucesso
http_response_code(200);
echo json_encode(['received' => true]);
Veja Assinatura HMAC pra detalhes da validação.
Exemplo — receptor em Express (Node.js)
const express = require('express');
const crypto = require('crypto');
const app = express;
app.use(express.raw({ type: 'application/json' })); // raw body pra HMAC
const SECRET = 'seu_secret_aqui';
app.post('/webhook', (req, res) => {
const signature = req.headers['x-syncro-signature'] || '';
const body = req.body.toString('utf8');
const expected = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = JSON.parse(body);
console.log(`Event: ${payload.event}`, payload.data);
res.status(200).json({ received: true });
});
app.listen(3000);
Boas práticas
1. Responda rápido (<5s)
Endpoint deve retornar 2xx em menos de 5 segundos. Se processamento demora, enfileire internamente:
// Recebe → marca como recebido → responde 200 → processa em background
queue_push(['event' => $event, 'data' => $data]);
http_response_code(200);
2. Idempotência sempre
Use X-Syncro-Delivery como dedup. Mesmo evento processado 2x não é problema.
3. Valide HMAC sempre
Sem validação, qualquer pessoa que descobriu sua URL pode mandar payloads falsos.
4. Use HTTPS
HTTP em texto puro expõe payload + headers (incluindo informação sensível de leads).
5. Monitore subscription
Crie alerta interno se failure_count > 5 — algo quebrou no seu lado.
6. Teste antes de produção
Use webhook.site ou ngrok local pra testar payloads antes de apontar pro endpoint final.
Limites
- Sem limite oficial de subscriptions por tenant.
- Sem limite de eventos por subscription.
- Rate de envio: depende do volume de eventos. Sistema não throttle outbound — pode mandar 1000 webhooks numa hora se 1000 leads foram criados.
Casos práticos
Sync com Google Sheets
Subscription lead.created → endpoint Apps Script → adiciona linha em planilha. Histórico permanente.
Notificação Slack
Subscription lead.won → endpoint Slack webhook → notifica canal #vendas com toast 🎉.
Atualizar ERP
Subscription lead.created, lead.won → endpoint ERP custom → cria cliente no ERP automaticamente.
Análise externa
Subscription lead.updated, lead.stage_changed → endpoint que envia evento pro Mixpanel, Amplitude, Looker etc.
Erros comuns
"Subscription criada mas eventos não chegam"
- Verifique
is_active=trueem GET /webhooks. - Confirme eventos certos (case-sensitive:
lead.created≠Lead.Created). - Verifique logs do seu endpoint — chegou request 4xx/5xx?
- Failure count crescendo → endpoint retorna erro.
"401 Invalid signature do meu lado"
Implementação HMAC errada. Veja Assinatura HMAC.
"Webhook desativado automaticamente"
Atingiu 10 falhas consecutivas. Investigue endpoint, corrija, reative com PUT /webhooks/{id} {"is_active": true}.
"SSRF blocked"
URL aponta pra IP privado. Use URL pública.
"Mesmo evento chega 5x"
Idempotência mal implementada no seu lado. Use X-Syncro-Delivery como dedup.
Próximos passos
- Pra entender HMAC, veja Assinatura HMAC.
- Pra usar n8n, veja n8n Community Node.