Eventos e payload

Lista de eventos suportados, formato das entregas, headers e validação da assinatura HMAC.

Todo evento chega por POST na URL cadastrada, com a mesma estrutura base e um conjunto de headers de identificação.

Eventos suportados

30 valores no enum WebhookEvent (fonte: schema Prisma).

DomínioEventos
WhatsAppWHATSAPP_RECEIVED, WHATSAPP_SENT, WHATSAPP_DELIVERED, WHATSAPP_READ, WHATSAPP_FAILED
E-mailEMAIL_SENT, EMAIL_DELIVERED, EMAIL_OPENED, EMAIL_CLICKED, EMAIL_BOUNCED, EMAIL_UNSUBSCRIBED, EMAIL_COMPLAINED
CampanhasCAMPAIGN_SCHEDULED, CAMPAIGN_STARTED, CAMPAIGN_COMPLETED, CAMPAIGN_FAILED, CAMPAIGN_PAUSED, CAMPAIGN_CANCELLED
Contatos (consent)CONTACT_SUBSCRIBED, CONTACT_UNSUBSCRIBED, CONTACT_CONSENT_GIVEN, CONTACT_CONSENT_REVOKED
Contatos / CRMCONTACT_CREATED, CONTACT_UPDATED, TAG_ADDED
ConversasCONVERSATION_CREATED, CONVERSATION_ASSIGNED, CONVERSATION_CLOSED
NegóciosDEAL_STAGE_CHANGED
FlowsFLOW_COMPLETED

Estrutura do payload

Estrutura base
{
  "event": "WHATSAPP_RECEIVED",
  "timestamp": "2026-02-04T18:00:00.000Z",
  "data": {},
  "metadata": {
    "tenantId": "workspace_...",
    "eventId": "..."
  }
}
WHATSAPP_RECEIVED
{
  "event": "WHATSAPP_RECEIVED",
  "timestamp": "2026-02-04T18:00:00.000Z",
  "data": {
    "message": {
      "id": "msg_...",
      "whatsappMessageId": "wamid...",
      "conversationId": "conv_...",
      "from": "+5511999999999",
      "to": "",
      "content": "Olá",
      "type": "text",
      "timestamp": "2026-02-04T18:00:00.000Z",
      "mediaUrl": null,
      "mediaType": null,
      "filename": null,
      "metadata": {}
    },
    "conversation": {
      "id": "conv_...",
      "status": "bot",
      "channel": "whatsapp",
      "contactName": "Maria"
    }
  },
  "metadata": {
    "tenantId": "workspace_...",
    "eventId": "wamid..."
  }
}
WHATSAPP_SENT
{
  "event": "WHATSAPP_SENT",
  "timestamp": "2026-02-04T18:00:00.000Z",
  "data": {
    "message": {
      "id": "msg_...",
      "whatsappMessageId": "wamid...",
      "conversationId": "conv_...",
      "status": "sent",
      "recipientId": "5511999999999",
      "timestamp": "1707079200"
    }
  },
  "metadata": {
    "tenantId": "workspace_...",
    "eventId": "wamid...:sent"
  }
}
EMAIL_SENT
{
  "event": "EMAIL_SENT",
  "timestamp": "2026-02-04T18:00:00.000Z",
  "data": {
    "campaignId": "camp_...",
    "recipientId": "rec_...",
    "email": "cliente@exemplo.com"
  },
  "metadata": {
    "tenantId": "workspace_..."
  }
}
DEAL_STAGE_CHANGED
{
  "event": "DEAL_STAGE_CHANGED",
  "timestamp": "2026-08-04T12:00:00.000Z",
  "data": {
    "dealId": "deal_...",
    "fromStageId": "stage_a",
    "toStageId": "stage_b",
    "pipelineId": "pipe_..."
  },
  "metadata": { "tenantId": "workspace_...", "eventId": "..." }
}
FLOW_COMPLETED
{
  "event": "FLOW_COMPLETED",
  "timestamp": "2026-08-04T12:00:00.000Z",
  "data": {
    "flowId": "flow_...",
    "executionId": "exec_...",
    "status": "COMPLETED"
  },
  "metadata": { "tenantId": "workspace_...", "eventId": "..." }
}
CONTACT_CREATED
{
  "event": "CONTACT_CREATED",
  "timestamp": "2026-08-04T12:00:00.000Z",
  "data": {
    "contactId": "ct_...",
    "phone": "+5511987654321"
  },
  "metadata": { "tenantId": "workspace_...", "eventId": "..." }
}

Headers de cada entrega

HeaderConteúdo
Content-Typeapplication/json
User-AgentLIP-Webhook/1.0
X-LIP-EventNome do evento
X-LIP-TimestampTimestamp da entrega
X-LIP-AttemptNúmero da tentativa
X-LIP-SignatureAssinatura HMAC, quando o secret está configurado

Assinatura HMAC

A assinatura é um HMAC SHA-256 do payload concatenado ao timestamp:

Fórmula
signature = HMAC_SHA256(secret, "{timestamp}.{payload_json}")

Sempre valide a assinatura

Antes de confiar em um evento, recalcule o HMAC com o seu secret e compare com X-LIP-Signature. Use o corpo bruto da requisição, não o JSON re-serializado, para o cálculo bater.

Validação em Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';

function isValidSignature(rawBody, timestamp, signature, secret) {
  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Retries e timeouts

  • O timeout de entrega é de 30 segundos.
  • Os retries seguem backoff exponencial, controlado por backoffMs e maxRetries.
  • Uma resposta 2xx marca o webhook como entregue; 4xx e 5xx disparam nova tentativa.

Deduplique pelo eventId

Como um evento pode ser reentregue, use metadata.eventId como chave de idempotência no seu lado antes de processar.