Webhooks para Notificações Automáticas

Os webhooks eliminam a necessidade de fazer polling constante, notificando sua aplicação quando eventos importantes acontecem.

Eventos Disponíveis

EventoQuando ocorre
note.completedQuando o processamento é concluído
note.failedQuando há erro no processamento
note.processingQuando o processamento inicia

Estrutura do Payload

{
  "event": "note.completed",
  "timestamp": "2024-02-14T18:25:43Z",
  "data": {
    "note_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "external_id": "CLIENTE_123"
  }
}

Implementações

Node.js + Express

const express = require('express');
const crypto = require('crypto');

const app = express();

// Middleware para webhook (raw body)
app.use('/webhook', express.raw({ type: 'application/json' }));

// Sua chave secreta do webhook (fornecida pela equipe ConnectVets)
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

function verifySignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature.replace('sha256=', ''), 'hex'),
    Buffer.from(expectedSignature, 'hex')
  );
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-connectvets-signature'];
  const payload = req.body;

  // Verificar assinatura
  if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Signature verification failed');
  }

  const event = JSON.parse(payload);
  
  // Processar evento
  switch (event.event) {
    case 'note.completed':
      handleNoteCompleted(event.data);
      break;
    case 'note.failed':
      handleNoteFailed(event.data);
      break;
    case 'note.processing':
      handleNoteProcessing(event.data);
      break;
  }

  res.status(200).send('OK');
});

async function handleNoteCompleted(data) {
  console.log(`Nota ${data.note_id} processada com sucesso`);
  
  // Buscar detalhes completos da nota
  const noteDetails = await fetchNoteDetails(data.note_id);
  
  // Atualizar seu sistema
  await updateLocalDatabase(noteDetails);
  
  // Notificar usuário
  await notifyUser(data.external_id, 'completed');
}

async function handleNoteFailed(data) {
  console.log(`Erro no processamento da nota ${data.note_id}`);
  
  // Registrar erro
  await logError(data);
  
  // Notificar sobre o erro
  await notifyUser(data.external_id, 'failed');
}

async function handleNoteProcessing(data) {
  console.log(`Processamento iniciado para nota ${data.note_id}`);
  
  // Atualizar status na interface
  await updateStatus(data.note_id, 'processing');
}

app.listen(3000, () => {
  console.log('Webhook listener rodando na porta 3000');
});

Python + Flask

from flask import Flask, request, abort
import hashlib
import hmac
import json
import os

app = Flask(__name__)

WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')

def verify_signature(payload, signature, secret):
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    received_signature = signature.replace('sha256=', '')
    
    return hmac.compare_digest(expected_signature, received_signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-ConnectVets-Signature')
    payload = request.get_data()
    
    # Verificar assinatura
    if not verify_signature(payload, signature, WEBHOOK_SECRET):
        abort(401)
    
    event = json.loads(payload)
    
    # Processar evento
    if event['event'] == 'note.completed':
        handle_note_completed(event['data'])
    elif event['event'] == 'note.failed':
        handle_note_failed(event['data'])
    elif event['event'] == 'note.processing':
        handle_note_processing(event['data'])
    
    return 'OK', 200

def handle_note_completed(data):
    print(f"Nota {data['note_id']} processada com sucesso")
    
    # Buscar detalhes da nota
    note_details = fetch_note_details(data['note_id'])
    
    # Atualizar banco de dados local
    update_local_database(note_details)
    
    # Enviar notificação
    notify_user(data.get('external_id'), 'completed')

def handle_note_failed(data):
    print(f"Erro no processamento da nota {data['note_id']}")
    
    # Registrar erro
    log_error(data)
    
    # Notificar sobre erro
    notify_user(data.get('external_id'), 'failed')

def handle_note_processing(data):
    print(f"Processamento iniciado para nota {data['note_id']}")
    
    # Atualizar status
    update_status(data['note_id'], 'processing')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)

PHP

<?php

// webhook.php

$webhookSecret = $_ENV['WEBHOOK_SECRET'];

function verifySignature($payload, $signature, $secret) {
    $expectedSignature = hash_hmac('sha256', $payload, $secret);
    $receivedSignature = str_replace('sha256=', '', $signature);
    
    return hash_equals($expectedSignature, $receivedSignature);
}

// Obter dados do webhook
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CONNECTVETS_SIGNATURE'] ?? '';

// Verificar assinatura
if (!verifySignature($payload, $signature, $webhookSecret)) {
    http_response_code(401);
    exit('Signature verification failed');
}

$event = json_decode($payload, true);

// Processar evento
switch ($event['event']) {
    case 'note.completed':
        handleNoteCompleted($event['data']);
        break;
    case 'note.failed':
        handleNoteFailed($event['data']);
        break;
    case 'note.processing':
        handleNoteProcessing($event['data']);
        break;
}

http_response_code(200);
echo 'OK';

function handleNoteCompleted($data) {
    error_log("Nota {$data['note_id']} processada com sucesso");
    
    // Buscar detalhes da nota
    $noteDetails = fetchNoteDetails($data['note_id']);
    
    // Atualizar banco de dados
    updateLocalDatabase($noteDetails);
    
    // Enviar notificação
    notifyUser($data['external_id'] ?? null, 'completed');
}

function handleNoteFailed($data) {
    error_log("Erro no processamento da nota {$data['note_id']}");
    
    // Registrar erro
    logError($data);
    
    // Notificar sobre erro
    notifyUser($data['external_id'] ?? null, 'failed');
}

function handleNoteProcessing($data) {
    error_log("Processamento iniciado para nota {$data['note_id']}");
    
    // Atualizar status
    updateStatus($data['note_id'], 'processing');
}
?>

Configuração do Webhook

Solicitar Configuração

Para configurar webhooks, entre em contato com nossa equipe via WhatsApp (+55 31 8512-7147) informando:
  • URL do endpoint: https://seudominio.com/webhook
  • Eventos desejados: ["note.completed", "note.failed", "note.processing"]
  • Ambiente: produção ou desenvolvimento
Nossa equipe configurará os webhooks e fornecerá a chave secreta para validação.

Validar Webhook

# Testar se seu endpoint está funcionando
curl -X POST "https://seudominio.com/webhook" \
  -H "Content-Type: application/json" \
  -H "X-ConnectVets-Signature: sha256=test" \
  -d '{"event":"test","data":{}}'

Segurança e Boas Práticas

1. Sempre Verificar Assinatura

// ❌ NUNCA faça isso
app.post('/webhook', (req, res) => {
  const event = req.body;
  processEvent(event); // Sem verificação!
});

// ✅ SEMPRE faça isso
app.post('/webhook', (req, res) => {
  if (!verifySignature(req.body, req.headers['x-connectvets-signature'], SECRET)) {
    return res.status(401).send('Unauthorized');
  }
  processEvent(req.body);
});

2. Ser Idempotente

const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const event = JSON.parse(req.body);
  
  // Evitar processar o mesmo evento duas vezes
  if (processedEvents.has(event.id)) {
    return res.status(200).send('Already processed');
  }
  
  processEvent(event);
  processedEvents.add(event.id);
  
  res.status(200).send('OK');
});

3. Responder Rapidamente

app.post('/webhook', (req, res) => {
  // Responder imediatamente
  res.status(200).send('OK');
  
  // Processar assincronamente
  setImmediate(() => {
    processEvent(JSON.parse(req.body));
  });
});

Testando Webhooks

Usando ngrok para Desenvolvimento

# Instalar ngrok
npm install -g ngrok

# Expor porta local
ngrok http 3000

# Usar URL do ngrok no webhook
# https://abc123.ngrok.io/webhook

Logs de Debug

app.post('/webhook', (req, res) => {
  console.log('Headers:', req.headers);
  console.log('Body:', req.body.toString());
  
  // ... resto do código
});

Importante: Sempre use HTTPS em produção e mantenha sua chave secreta segura.