Skip to main content
Exemplo de servidor pronto para produção que recebe, valida e roteia eventos.
// webhook-server.ts
import express from 'express';
import crypto from 'crypto';

const app = express();
const SECRET = process.env.CORDIALY_WEBHOOK_SECRET!;

// ⚠️ IMPORTANTE: usar raw body para validar a assinatura
app.use('/webhook/cordialy', express.raw({ type: 'application/json' }));

function validarAssinatura(payload: Buffer, signature: string): boolean {
  const esperado = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(payload)
    .digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(esperado));
  } catch {
    return false;
  }
}

app.post('/webhook/cordialy', async (req, res) => {
  const signature = req.headers['x-cordialy-signature'] as string;

  if (!signature || !validarAssinatura(req.body, signature)) {
    return res.status(401).json({ error: 'Assinatura inválida' });
  }

  // Responde imediatamente — processa em background
  res.status(200).json({ received: true });

  const evento = JSON.parse(req.body.toString());
  await processarEvento(evento);
});

async function processarEvento(evento: any) {
  console.log(`[webhook] ${evento.event} — lead: ${evento.data?.lead_id}`);

  switch (evento.event) {
    case 'lead.created':
      await onLeadCriado(evento.data);
      break;
    case 'lead.status_changed':
      await onStatusAlterado(evento.data);
      break;
    case 'message.received':
      await onMensagemRecebida(evento.data);
      break;
    case 'session.ended':
      await onSessaoEncerrada(evento.data);
      break;
    default:
      console.log(`[webhook] evento não tratado: ${evento.event}`);
  }
}

async function onLeadCriado(data: any) {
  // Sincroniza com seu CRM
  await crm.createContact({
    phone: data.customer_phone,
    name: data.name,
    externalId: data.lead_id,
  });
}

async function onStatusAlterado(data: any) {
  if (data.new_status === 'converted') {
    await crm.markAsWon(data.lead_id);
    await notificar(`Lead ${data.lead_id} convertido!`);
  }
}

async function onMensagemRecebida(data: any) {
  // Notifica o time de vendas
  await slack.send(`#vendas`, `Nova mensagem do lead ${data.lead_id}: "${data.content}"`);
}

async function onSessaoEncerrada(data: any) {
  await crm.updateDeal(data.lead_id, {
    status: data.status,
    duration: calcularDuracao(data.started_at, data.ended_at),
  });
}

app.listen(3000, () => console.log('Webhook server rodando na porta 3000'));
Use express.raw() no Node.js e $request->getContent() no PHP para obter o body bruto. Se usar express.json() antes, o body já foi parseado e a assinatura não bate.