Webhooks assinados, com retry.
Receba eventos de play, retenção, CTA e intenção em qualquer endpoint HTTPS. Assinatura HMAC-SHA256 sobre {timestamp}.{rawBody}, retry com backoff e logs no painel.
Cadastrar e gerar secret
Em Integrações → Webhooks → Novo webhook, informe nome, URL HTTPS e (opcionalmente) os eventos que deseja receber. O LeadPlayer gera automaticamente um secret forte no formato whsec_… usando um RNG criptográfico do servidor. O valor completo é mostrado apenas no momento da criação ou regeneração — depois disso, o painel exibe somente um prefixo e os últimos caracteres.
Para integrações externas (LeadsPulse, n8n, Make, Zapier, WhatsApp), preencha o campo webhook_secret no painel da integração. Sem secret configurado, o LeadPlayer não envia eventos para aquele destino — isso é registrado como security_event (webhook_missing_secret ou integration_missing_webhook_secret) sem vazar URL completa nem secret.
Eventos disponíveis
| Evento | Descrição | Status |
|---|---|---|
| player_loaded | Player montou no DOM e está pronto para tocar. | ativo |
| video_view | Sessão inicial registrada (visitante carregou o vídeo). | ativo |
| video_play | Primeiro play da sessão. | ativo |
| video_pause | Pausa manual. | ativo |
| video_resume | Retomada após pause. | ativo |
| video_unmute | Visitante destravou o áudio (sinal forte de intenção). | ativo |
| watched_25_percent | Atingiu 25% do tempo total assistido. | ativo |
| watched_50_percent | Atingiu 50%. | ativo |
| watched_75_percent | Atingiu 75%. | ativo |
| watched_90_percent | Atingiu 90% (considerado complete). | ativo |
| cta_shown | Um CTA com delay apareceu para o visitante. | ativo |
| cta_clicked | Visitante clicou no CTA. | ativo |
| session_ended | Sessão encerrada (unload, navegação ou heartbeat expirado). | ativo |
| reached_pitch | Visitante chegou ao trecho de oferta configurado. | em breve |
| high_intent_lead | Lead Intent Score cruzou o limiar configurado. | em breve |
| conversion | Conversão server-side confirmada via /api/public/conversion. | em breve |
| abandoned_before_pitch | Saiu antes do pitch. | em breve |
| abandoned_after_offer | Saiu logo após a oferta. | em breve |
Headers e formato da requisição
X-Player-Event— nome do evento (ex.:cta_clicked).X-Player-Signature— HMAC-SHA256 em hex (sem prefixo).X-Player-Timestamp— Unix epoch em segundos no momento da assinatura.X-Player-Delivery-Id— UUID estável da entrega; use para idempotência.Content-Type: application/json.
POST /seu-endpoint HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
X-Player-Event: cta_clicked
X-Player-Signature: 4a9f3c... (hex)
X-Player-Timestamp: 1751155200
X-Player-Delivery-Id: 9b1c8d3e-4f5a-4b2d-9c10-3a1b2c3d4e5f
{
"tenant_id": "uuid",
"video_id": "uuid",
"session_id": "ses_xxx",
"visitor_id": "vis_xxx",
"event_name": "cta_clicked",
"current_time": 252,
"duration": 480,
"watched_percentage": 52.5,
"utm_source": "meta",
"utm_campaign": "vsl-01",
"ctwa_clid": "...",
"external_lead_id": "...",
"created_at": "2026-06-28T00:00:00.000Z"
}Verificar a assinatura (Node/TypeScript)
A base da assinatura é a string ${timestamp}.${rawBody}. Use sempre o corpo bruto (string como veio na requisição); qualquer reformatação por JSON.parse + JSON.stringify muda a assinatura. Compare com timingSafeEqual e rejeite timestamps fora de uma janela de 5 minutos para mitigar replay attacks.
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 5 * 60;
export function verifyLeadPlayerWebhook(opts: {
rawBody: string;
signatureHeader: string | null;
timestampHeader: string | null;
secret: string;
}): boolean {
const { rawBody, signatureHeader, timestampHeader, secret } = opts;
if (!signatureHeader || !timestampHeader) return false;
const ts = Number(timestampHeader);
if (!Number.isFinite(ts)) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - ts) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(signatureHeader, "hex");
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}Política de retry
Considera-se sucesso qualquer resposta HTTP 2xx em até 10 s. Qualquer outro status, timeout ou erro de rede é tratado como falha.
- Tentativa 1 — imediata, no momento do evento.
- Tentativa 2 — após 1 minuto.
- Tentativa 3 — após 5 minutos.
- Tentativa 4 — após 30 minutos.
- Após 4 falhas a entrega vai para
status = failede fica disponível para reprocesso manual em Integrações → Logs.
Boas práticas no receiver
- Responder 2xx rápido (ack) e processar em fila assíncrona.
- Persistir
X-Player-Delivery-Idpara garantir idempotência — eventos podem repetir após retry. - Validar a assinatura antes de qualquer side-effect.
- Rejeitar requisições sem
X-Player-Signatureou com timestamp fora da janela. - Não confiar em campos sensíveis (tenant_id, valores monetários) sem cruzar com seu próprio banco.
- Manter o secret apenas em variável de ambiente do receiver — nunca no front-end.
Perguntas frequentes
Quais eventos são entregues hoje?
player_loaded, video_view, video_play, video_pause, video_resume, video_unmute, watched_25_percent, watched_50_percent, watched_75_percent, watched_90_percent, cta_shown, cta_clicked, session_ended. Eventos sinalizados como em breve (reached_pitch, high_intent_lead, conversion, abandoned_before_pitch, abandoned_after_offer) já existem como destinos válidos mas ainda não são disparados automaticamente.
Qual a política de retry?
Até 4 tentativas por entrega. Primeira é imediata, depois 1 min, 5 min e 30 min. Considera-se sucesso qualquer 2xx. Acima de 4 falhas a entrega entra em status failed e aparece em Integrações → Logs para reprocessamento manual.
Como o LeadPlayer assina o payload?
HMAC-SHA256 sobre a string `${timestamp}.${rawBody}`, usando o secret do endpoint (ou do external_integrations.config.webhook_secret). A assinatura vai em hex no header X-Player-Signature, e o timestamp Unix em X-Player-Timestamp. Recomendamos rejeitar requisições com timestamp fora de uma janela de 5 minutos para mitigar replay.
O que acontece se um destino estiver sem secret?
O LeadPlayer NÃO envia o evento. Não existe secret padrão compartilhado. A tentativa fica registrada como security_event (webhook_missing_secret ou integration_missing_webhook_secret) com tenant_id, endpoint_id e o host de destino — sem URL completa nem secret.
Posso testar webhook em desenvolvimento?
Sim. Use ngrok ou Cloudflare Tunnel para expor seu endpoint e cadastre a URL em Integrações → Webhooks. Existe um botão Testar que enfileira um evento player_loaded contra o destino.
Pronto para testar no seu vídeo?
Plano grátis sem cartão. Hospede um vídeo, embede no seu site e veja a retenção em minutos. Player sem marca a partir do Pro.