Webhooks de Verificação Email: Guia Assíncrono

Leo
LeoFounder, BillionVerify

Implemente webhooks de verificacao para processamento assincrono. Guia cobrindo configuracao, seguranca e tratamento de erros.

Cover Image for Webhooks de Verificação Email: Guia Assíncrono

Quando você está verificando milhares ou milhões de endereços de email, aguardar sincronicamente por cada resultado não é prático. Os webhooks de verificação de email fornecem uma solução elegante ao notificar sua aplicação quando as tarefas de verificação são concluídas, eliminando a necessidade de polling constante e habilitando fluxos de trabalho assíncronos eficientes. Este guia abrangente explora tudo o que os desenvolvedores precisam saber sobre implementação de webhooks de verificação de email, desde configuração básica até padrões avançados para lidar com operações de verificação em larga escala.

Entendendo Webhooks de Verificação de Email

Webhooks são callbacks HTTP que entregam dados à sua aplicação quando eventos específicos ocorrem. No contexto de verificação de email, webhooks notificam seus sistemas quando trabalhos de verificação em massa são concluídos, quando verificações individuais de email terminam no modo assíncrono, ou quando outros eventos significativos ocorrem durante o processo de verificação.

Por Que Usar Webhooks para Verificação de Email?

Os padrões tradicionais de requisição-resposta funcionam bem para verificação de email único, mas operações em massa apresentam desafios. Verificar 100.000 emails pode levar horas, e manter uma conexão HTTP aberta por tanto tempo não é viável. Fazer polling para atualizações de status desperdiça recursos e cria carga desnecessária na API.

Eliminação de Sobrecarga de Polling

Sem webhooks, você precisaria consultar repetidamente a API para verificar se os trabalhos em massa foram concluídos. Isso cria tráfego de rede desnecessário, consome limites de taxa da API e adiciona complexidade à sua aplicação. Os webhooks enviam notificações exatamente quando são necessárias.

Processamento em Tempo Real

Os webhooks permitem ação imediata quando a verificação é concluída. Sua aplicação pode processar resultados, atualizar bancos de dados e acionar ações subsequentes sem atrasos introduzidos por intervalos de polling.

Arquitetura Escalável

Arquiteturas baseadas em webhooks escalam naturalmente. Seja processando um trabalho em massa ou centenas simultaneamente, seu endpoint de webhook recebe notificações conforme chegam, e você pode processá-las de forma assíncrona usando filas ou workers.

Eficiência de Recursos

Em vez de manter conexões ou executar loops de polling, sua aplicação permanece ociosa até que os webhooks cheguem. Isso reduz custos de computação e simplifica os requisitos de infraestrutura.

Eventos de Webhook na Verificação de Email

Serviços de verificação de email tipicamente acionam webhooks para vários tipos de eventos:

Conclusão de Trabalho em Massa

O evento de webhook mais comum é disparado quando um trabalho de verificação em massa termina o processamento. A payload inclui status do trabalho, estatísticas resumidas e informações sobre download de resultados.

Progresso de Trabalho em Massa

Alguns serviços enviam webhooks de progresso em intervalos durante o processamento em massa, permitindo que você acompanhe o progresso da verificação e estime o tempo de conclusão.

Falha de Trabalho em Massa

Quando um trabalho em massa encontra erros que impedem a conclusão, webhooks de falha fornecem detalhes sobre o que deu errado e se resultados parciais estão disponíveis.

Verificação de Email Único (Modo Assíncrono)

Para cenários de verificação em tempo real de alto volume, a verificação assíncrona de email único envia resultados via webhook em vez de aguardar resposta síncrona.

Configurando Endpoints de Webhook

Implementar webhooks requer criar um endpoint em sua aplicação que possa receber e processar payloads de webhook.

Estrutura Básica do Endpoint

Um endpoint de webhook é simplesmente um endpoint HTTP POST que aceita payloads JSON:

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

app.use(express.json());

app.post('/webhooks/email-verification', async (req, res) => {
  const { event_type, job_id, status, data } = req.body;

  console.log(`Received webhook: ${event_type} for job ${job_id}`);

  // Process the webhook
  try {
    await handleWebhookEvent(req.body);

    // Always respond quickly to acknowledge receipt
    res.status(200).json({ received: true });
  } catch (error) {
    console.error('Webhook processing error:', error);

    // Still acknowledge receipt to prevent retries
    res.status(200).json({ received: true, error: error.message });
  }
});

async function handleWebhookEvent(payload) {
  switch (payload.event_type) {
    case 'bulk.completed':
      await handleBulkCompleted(payload);
      break;
    case 'bulk.failed':
      await handleBulkFailed(payload);
      break;
    case 'bulk.progress':
      await handleBulkProgress(payload);
      break;
    default:
      console.log(`Unknown event type: ${payload.event_type}`);
  }
}

Melhores Práticas de Resposta de Webhook

Serviços de verificação de email esperam respostas rápidas dos endpoints de webhook. Se seu endpoint demorar muito para responder, o serviço pode assumir que a entrega falhou e tentar novamente.

Responda Imediatamente

Reconheça o recebimento do webhook imediatamente, depois processe a payload de forma assíncrona:

app.post('/webhooks/email-verification', async (req, res) => {
  // Immediately acknowledge receipt
  res.status(200).json({ received: true });

  // Process asynchronously
  setImmediate(async () => {
    try {
      await handleWebhookEvent(req.body);
    } catch (error) {
      console.error('Async webhook processing error:', error);
      // Log for retry or manual processing
      await logFailedWebhook(req.body, error);
    }
  });
});

Use Filas de Mensagens para Processamento Pesado

Para sistemas de produção, enfileire payloads de webhook para processamento por processos worker:

const Queue = require('bull');
const webhookQueue = new Queue('email-verification-webhooks');

app.post('/webhooks/email-verification', async (req, res) => {
  // Queue the webhook for processing
  await webhookQueue.add('process-webhook', req.body, {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 1000
    }
  });

  res.status(200).json({ received: true });
});

// Worker process
webhookQueue.process('process-webhook', async (job) => {
  const payload = job.data;
  await handleWebhookEvent(payload);
});

Configurando Webhooks com a API

Registre seu endpoint de webhook com o serviço de verificação de email:

async function registerWebhook(webhookUrl, events, secret) {
  const response = await fetch('https://api.billionverify.com/v1/webhooks', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.BV_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: webhookUrl,
      events: events,
      secret: secret
    })
  });

  const result = await response.json();

  if (!response.ok) {
    throw new Error(`Failed to register webhook: ${result.error}`);
  }

  console.log(`Webhook registered: ${result.webhook_id}`);
  return result;
}

// Register for bulk job events
await registerWebhook(
  'https://yourapp.com/webhooks/email-verification',
  ['bulk.completed', 'bulk.failed', 'bulk.progress'],
  process.env.WEBHOOK_SECRET
);

Protegendo Endpoints de Webhook

Endpoints de webhook são acessíveis publicamente, tornando a segurança essencial. Sem verificação adequada, atacantes poderiam enviar payloads de webhook falsas para manipular sua aplicação.

Verificação de Assinatura

A maioria dos serviços de verificação de email assina payloads de webhook usando HMAC-SHA256 com um segredo compartilhado. Verifique assinaturas antes de processar:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhooks/email-verification', async (req, res) => {
  const signature = req.headers['x-webhook-signature'];

  if (!signature) {
    return res.status(401).json({ error: 'Missing signature' });
  }

  const isValid = verifyWebhookSignature(
    req.body,
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    console.warn('Invalid webhook signature received');
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Signature valid, process webhook
  await handleWebhookEvent(req.body);
  res.status(200).json({ received: true });
});

Validação de Timestamp

Previna ataques de replay validando timestamps de webhook:

function isTimestampValid(timestamp, toleranceSeconds = 300) {
  const webhookTime = new Date(timestamp).getTime();
  const currentTime = Date.now();
  const difference = Math.abs(currentTime - webhookTime);

  return difference <= toleranceSeconds * 1000;
}

app.post('/webhooks/email-verification', async (req, res) => {
  const { timestamp } = req.body;

  if (!isTimestampValid(timestamp)) {
    console.warn('Webhook timestamp outside acceptable range');
    return res.status(400).json({ error: 'Invalid timestamp' });
  }

  // Continue with signature verification and processing
});

Lista de Permissão de IPs

Para segurança adicional, restrinja o acesso ao webhook a endereços IP conhecidos:

const allowedIPs = [
  '203.0.113.0/24',  // BillionVerify webhook servers
  '198.51.100.0/24'
];

function isIPAllowed(clientIP) {
  // Implement CIDR range checking
  return allowedIPs.some(range => isIPInRange(clientIP, range));
}

app.post('/webhooks/email-verification', async (req, res) => {
  const clientIP = req.ip || req.connection.remoteAddress;

  if (!isIPAllowed(clientIP)) {
    console.warn(`Webhook from unauthorized IP: ${clientIP}`);
    return res.status(403).json({ error: 'Forbidden' });
  }

  // Continue with processing
});

Tratamento de Idempotência

Webhooks podem ser entregues múltiplas vezes devido a problemas de rede ou tentativas. Implemente idempotência para lidar com duplicatas de forma segura:

const processedWebhooks = new Set(); // Use Redis in production

async function handleWebhookIdempotent(payload) {
  const webhookId = payload.webhook_id || payload.event_id;

  // Check if already processed
  if (processedWebhooks.has(webhookId)) {
    console.log(`Duplicate webhook ignored: ${webhookId}`);
    return;
  }

  // Mark as processing
  processedWebhooks.add(webhookId);

  try {
    await handleWebhookEvent(payload);
  } catch (error) {
    // Remove from processed set to allow retry
    processedWebhooks.delete(webhookId);
    throw error;
  }
}

Para sistemas de produção, use Redis para idempotência distribuída:

const Redis = require('ioredis');
const redis = new Redis();

async function isWebhookProcessed(webhookId) {
  const key = `webhook:processed:${webhookId}`;
  const result = await redis.set(key, '1', 'NX', 'EX', 86400); // 24 hour expiry
  return result === null; // Already exists
}

app.post('/webhooks/email-verification', async (req, res) => {
  const webhookId = req.body.webhook_id;

  if (await isWebhookProcessed(webhookId)) {
    console.log(`Duplicate webhook: ${webhookId}`);
    return res.status(200).json({ received: true, duplicate: true });
  }

  await handleWebhookEvent(req.body);
  res.status(200).json({ received: true });
});

Processando Payloads de Webhook

Diferentes eventos de webhook requerem diferentes lógicas de tratamento. Vamos explorar padrões comuns para processar webhooks de verificação de email.

Tratando Conclusão de Trabalho em Massa

Quando um trabalho de verificação em massa é concluído, baixe e processe os resultados:

async function handleBulkCompleted(payload) {
  const { job_id, status, summary, download_url } = payload;

  console.log(`Bulk job ${job_id} completed with status: ${status}`);
  console.log(`Summary: ${summary.valid} valid, ${summary.invalid} invalid`);

  // Download results
  const results = await downloadResults(download_url);

  // Process results
  await processVerificationResults(job_id, results);

  // Update job status in database
  await updateJobStatus(job_id, 'completed', summary);

  // Notify relevant parties
  await sendCompletionNotification(job_id, summary);
}

async function downloadResults(url) {
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${process.env.BV_API_KEY}`
    }
  });

  if (!response.ok) {
    throw new Error(`Failed to download results: ${response.status}`);
  }

  return await response.json();
}

async function processVerificationResults(jobId, results) {
  // Batch update contacts in database
  const validEmails = results.filter(r => r.is_valid);
  const invalidEmails = results.filter(r => !r.is_valid);

  await db.transaction(async (trx) => {
    // Update valid emails
    for (const batch of chunkArray(validEmails, 1000)) {
      await trx('contacts')
        .whereIn('email', batch.map(r => r.email))
        .update({
          email_verified: true,
          verification_date: new Date(),
          verification_job_id: jobId
        });
    }

    // Handle invalid emails
    for (const batch of chunkArray(invalidEmails, 1000)) {
      await trx('contacts')
        .whereIn('email', batch.map(r => r.email))
        .update({
          email_verified: false,
          email_invalid_reason: trx.raw('CASE email ' +
            batch.map(r => `WHEN '${r.email}' THEN '${r.reason}'`).join(' ') +
            ' END'),
          verification_date: new Date(),
          verification_job_id: jobId
        });
    }
  });
}

Tratando Falhas de Trabalho em Massa

Quando trabalhos falham, capture informações de erro e determine se a recuperação é possível:

async function handleBulkFailed(payload) {
  const { job_id, error_code, error_message, partial_results_available } = payload;

  console.error(`Bulk job ${job_id} failed: ${error_message}`);

  // Update job status
  await updateJobStatus(job_id, 'failed', {
    error_code,
    error_message
  });

  // Try to retrieve partial results if available
  if (partial_results_available) {
    console.log('Attempting to retrieve partial results...');
    try {
      const partialResults = await downloadPartialResults(job_id);
      await processVerificationResults(job_id, partialResults);

      // Identify unprocessed emails for retry
      const processedEmails = new Set(partialResults.map(r => r.email));
      const originalEmails = await getOriginalJobEmails(job_id);
      const unprocessedEmails = originalEmails.filter(e => !processedEmails.has(e));

      if (unprocessedEmails.length > 0) {
        // Schedule retry for unprocessed emails
        await scheduleRetryJob(job_id, unprocessedEmails);
      }
    } catch (error) {
      console.error('Failed to retrieve partial results:', error);
    }
  }

  // Notify about failure
  await sendFailureNotification(job_id, error_message);
}

async function scheduleRetryJob(originalJobId, emails) {
  // Create new job for remaining emails
  const response = await fetch('https://api.billionverify.com/v1/bulk/verify', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.BV_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      emails,
      metadata: {
        retry_of: originalJobId
      }
    })
  });

  const { job_id: newJobId } = await response.json();
  console.log(`Scheduled retry job ${newJobId} for ${emails.length} emails`);
}

Tratando Atualizações de Progresso

Webhooks de progresso ajudam a acompanhar trabalhos de longa duração:

async function handleBulkProgress(payload) {
  const { job_id, processed_count, total_count, estimated_completion } = payload;

  const percentComplete = Math.round((processed_count / total_count) * 100);
  console.log(`Job ${job_id}: ${percentComplete}% complete (${processed_count}/${total_count})`);

  // Update progress in database
  await updateJobProgress(job_id, {
    processed_count,
    total_count,
    percent_complete: percentComplete,
    estimated_completion: new Date(estimated_completion)
  });

  // Optionally notify users of progress
  if (percentComplete % 25 === 0) {
    await sendProgressNotification(job_id, percentComplete);
  }
}

Padrões Avançados de Webhook

Sistemas de produção se beneficiam de padrões avançados que melhoram a confiabilidade e a manutenibilidade.

Fila de Mensagens Mortas para Webhooks Falhados

Quando o processamento de webhook falha repetidamente, mova as payloads para uma fila de mensagens mortas para revisão manual:

const webhookQueue = new Queue('email-verification-webhooks');
const deadLetterQueue = new Queue('webhook-dead-letters');

webhookQueue.process('process-webhook', async (job) => {
  try {
    await handleWebhookEvent(job.data);
  } catch (error) {
    // Check if this is the final retry
    if (job.attemptsMade >= job.opts.attempts - 1) {
      // Move to dead letter queue
      await deadLetterQueue.add('failed-webhook', {
        original_payload: job.data,
        error: error.message,
        failed_at: new Date().toISOString(),
        attempts: job.attemptsMade + 1
      });
    }
    throw error; // Re-throw to trigger retry
  }
});

// Process dead letters manually or with alerts
deadLetterQueue.on('completed', async (job) => {
  await sendAlert({
    type: 'webhook_dead_letter',
    job_id: job.data.original_payload.job_id,
    error: job.data.error
  });
});

Event Sourcing de Webhook

Armazene todos os eventos de webhook para trilhas de auditoria e capacidade de replay:

async function handleWebhookWithEventSourcing(payload) {
  // Store raw event
  const eventId = await storeWebhookEvent(payload);

  try {
    // Process event
    await handleWebhookEvent(payload);

    // Mark as processed
    await markEventProcessed(eventId);
  } catch (error) {
    // Mark as failed
    await markEventFailed(eventId, error);
    throw error;
  }
}

async function storeWebhookEvent(payload) {
  const result = await db('webhook_events').insert({
    event_type: payload.event_type,
    job_id: payload.job_id,
    payload: JSON.stringify(payload),
    received_at: new Date(),
    status: 'pending'
  });

  return result[0];
}

// Replay failed events
async function replayFailedEvents() {
  const failedEvents = await db('webhook_events')
    .where('status', 'failed')
    .where('retry_count', '<', 3);

  for (const event of failedEvents) {
    try {
      await handleWebhookEvent(JSON.parse(event.payload));
      await markEventProcessed(event.id);
    } catch (error) {
      await incrementRetryCount(event.id);
    }
  }
}

Roteamento de Webhook Multi-Tenant

Para aplicações SaaS, encaminhe webhooks para handlers específicos do tenant:

async function handleMultiTenantWebhook(payload) {
  const { tenant_id, event_type, data } = payload;

  // Get tenant configuration
  const tenant = await getTenantConfig(tenant_id);

  if (!tenant) {
    console.error(`Unknown tenant: ${tenant_id}`);
    return;
  }

  // Route to tenant-specific handler
  switch (event_type) {
    case 'bulk.completed':
      await handleTenantBulkCompleted(tenant, data);
      break;
    case 'bulk.failed':
      await handleTenantBulkFailed(tenant, data);
      break;
  }

  // Forward to tenant webhook if configured
  if (tenant.webhook_url) {
    await forwardToTenant(tenant.webhook_url, tenant.webhook_secret, payload);
  }
}

async function forwardToTenant(url, secret, payload) {
  const signature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Webhook-Signature': signature
    },
    body: JSON.stringify(payload)
  });
}

Tratamento de Erros e Confiabilidade

Implementações robustas de webhook lidam com falhas graciosamente e garantem que nenhum dado seja perdido.

Estratégias de Retry

Implemente backoff exponencial para falhas transitórias:

async function processWebhookWithRetry(payload, maxRetries = 5) {
  const delays = [1000, 5000, 30000, 120000, 300000]; // 1s, 5s, 30s, 2m, 5m

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      await handleWebhookEvent(payload);
      return; // Success
    } catch (error) {
      const isRetryable = isRetryableError(error);

      if (!isRetryable || attempt === maxRetries - 1) {
        // Log to dead letter queue
        await logFailedWebhook(payload, error, attempt + 1);
        throw error;
      }

      console.log(`Retry ${attempt + 1}/${maxRetries} after ${delays[attempt]}ms`);
      await sleep(delays[attempt]);
    }
  }
}

function isRetryableError(error) {
  // Network errors, timeouts, and 5xx responses are retryable
  const retryableCodes = ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND'];
  return retryableCodes.includes(error.code) ||
         (error.status && error.status >= 500);
}

Padrão Circuit Breaker

Previna falhas em cascata quando serviços downstream estão indisponíveis:

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 60000;
    this.state = 'CLOSED';
    this.failures = 0;
    this.lastFailure = null;
  }

  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailure > this.resetTimeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failures++;
    this.lastFailure = Date.now();

    if (this.failures >= this.failureThreshold) {
      this.state = 'OPEN';
      console.warn('Circuit breaker opened due to failures');
    }
  }
}

const databaseCircuitBreaker = new CircuitBreaker();

async function handleBulkCompletedSafely(payload) {
  await databaseCircuitBreaker.execute(async () => {
    await processVerificationResults(payload.job_id, payload.results);
  });
}

Monitoramento e Alertas

Rastreie métricas de saúde do webhook:

const metrics = {
  received: 0,
  processed: 0,
  failed: 0,
  latency: []
};

app.post('/webhooks/email-verification', async (req, res) => {
  const startTime = Date.now();
  metrics.received++;

  try {
    await handleWebhookEvent(req.body);
    metrics.processed++;
  } catch (error) {
    metrics.failed++;
    throw error;
  } finally {
    metrics.latency.push(Date.now() - startTime);

    // Keep only last 1000 measurements
    if (metrics.latency.length > 1000) {
      metrics.latency.shift();
    }
  }

  res.status(200).json({ received: true });
});

// Expose metrics endpoint
app.get('/metrics/webhooks', (req, res) => {
  const avgLatency = metrics.latency.reduce((a, b) => a + b, 0) / metrics.latency.length;

  res.json({
    received: metrics.received,
    processed: metrics.processed,
    failed: metrics.failed,
    success_rate: (metrics.processed / metrics.received * 100).toFixed(2) + '%',
    avg_latency_ms: Math.round(avgLatency)
  });
});

// Alert on high failure rate
setInterval(() => {
  const failureRate = metrics.failed / metrics.received;

  if (failureRate > 0.1) { // More than 10% failures
    sendAlert({
      type: 'high_webhook_failure_rate',
      failure_rate: failureRate,
      total_received: metrics.received,
      total_failed: metrics.failed
    });
  }
}, 60000);

Testando Implementações de Webhook

Testes completos garantem que os handlers de webhook funcionem corretamente em produção.

Testes Locais com ngrok

Use ngrok para expor endpoints locais para testes de webhook:

# Start your local server
node server.js

# In another terminal, expose it via ngrok
ngrok http 3000

Registre a URL do ngrok como seu endpoint de webhook durante o desenvolvimento.

Payloads de Webhook Simuladas

Crie fixtures de teste para diferentes tipos de eventos:

const mockPayloads = {
  bulkCompleted: {
    event_type: 'bulk.completed',
    job_id: 'job_123456',
    status: 'completed',
    timestamp: new Date().toISOString(),
    summary: {
      total: 1000,
      valid: 850,
      invalid: 120,
      risky: 30
    },
    download_url: 'https://api.billionverify.com/v1/bulk/download/job_123456'
  },

  bulkFailed: {
    event_type: 'bulk.failed',
    job_id: 'job_789012',
    error_code: 'PROCESSING_ERROR',
    error_message: 'Internal processing error',
    partial_results_available: true
  },

  bulkProgress: {
    event_type: 'bulk.progress',
    job_id: 'job_345678',
    processed_count: 5000,
    total_count: 10000,
    estimated_completion: new Date(Date.now() + 3600000).toISOString()
  }
};

// Test endpoint
describe('Webhook Handler', () => {
  it('should process bulk.completed event', async () => {
    const response = await request(app)
      .post('/webhooks/email-verification')
      .set('X-Webhook-Signature', generateSignature(mockPayloads.bulkCompleted))
      .send(mockPayloads.bulkCompleted);

    expect(response.status).toBe(200);
    expect(response.body.received).toBe(true);

    // Verify side effects
    const job = await db('verification_jobs').where('job_id', 'job_123456').first();
    expect(job.status).toBe('completed');
  });
});

Testes de Integração

Teste o fluxo completo do webhook incluindo verificação de assinatura:

describe('Webhook Security', () => {
  it('should reject requests without signature', async () => {
    const response = await request(app)
      .post('/webhooks/email-verification')
      .send(mockPayloads.bulkCompleted);

    expect(response.status).toBe(401);
  });

  it('should reject requests with invalid signature', async () => {
    const response = await request(app)
      .post('/webhooks/email-verification')
      .set('X-Webhook-Signature', 'invalid_signature')
      .send(mockPayloads.bulkCompleted);

    expect(response.status).toBe(401);
  });

  it('should accept requests with valid signature', async () => {
    const signature = generateSignature(mockPayloads.bulkCompleted);

    const response = await request(app)
      .post('/webhooks/email-verification')
      .set('X-Webhook-Signature', signature)
      .send(mockPayloads.bulkCompleted);

    expect(response.status).toBe(200);
  });
});

Integração de Webhook do BillionVerify

O BillionVerify fornece suporte abrangente de webhook para eventos de verificação de email, facilitando a construção de fluxos de trabalho de verificação assíncronos.

Configurando Webhooks

Configure webhooks através do painel do BillionVerify ou da API:

// Register webhook via API
async function setupBillionVerifyWebhooks() {
  const webhook = await registerWebhook(
    'https://yourapp.com/webhooks/emailverify',
    ['bulk.completed', 'bulk.failed', 'bulk.progress'],
    process.env.EMAILVERIFY_WEBHOOK_SECRET
  );

  console.log('Webhook configured:', webhook);
}

Formato da Payload do Webhook

Webhooks do BillionVerify incluem informações abrangentes sobre eventos de verificação:

{
  "event_type": "bulk.completed",
  "webhook_id": "wh_abc123",
  "job_id": "job_xyz789",
  "timestamp": "2025-01-15T10:30:00Z",
  "status": "completed",
  "summary": {
    "total": 10000,
    "valid": 8500,
    "invalid": 1200,
    "risky": 300,
    "disposable": 150,
    "catch_all": 200
  },
  "processing_time_ms": 45000,
  "download_url": "https://api.billionverify.com/v1/bulk/download/job_xyz789"
}

Exemplo de Integração Completa

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

const app = express();
app.use(express.json());

// Webhook endpoint for BillionVerify
app.post('/webhooks/emailverify', async (req, res) => {
  // Verify signature
  const signature = req.headers['x-emailverify-signature'];
  const isValid = verifySignature(req.body, signature);

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Acknowledge immediately
  res.status(200).json({ received: true });

  // Process asynchronously
  processWebhookAsync(req.body);
});

async function processWebhookAsync(payload) {
  try {
    switch (payload.event_type) {
      case 'bulk.completed':
        await handleBulkCompleted(payload);
        break;
      case 'bulk.failed':
        await handleBulkFailed(payload);
        break;
      case 'bulk.progress':
        await handleBulkProgress(payload);
        break;
    }
  } catch (error) {
    console.error('Webhook processing error:', error);
    await logFailedWebhook(payload, error);
  }
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Conclusão

Webhooks de verificação de email transformam a forma como as aplicações lidam com verificação em massa, habilitando processamento assíncrono eficiente, escalável e confiável. Ao implementar tratamento adequado de webhooks com medidas de segurança, tratamento de erros e monitoramento, você pode construir fluxos de trabalho robustos de verificação de email que escalam com as necessidades da sua aplicação.

Principais pontos para implementar webhooks de verificação de email:

  1. Responda rapidamente às requisições de webhook e processe payloads de forma assíncrona
  2. Verifique assinaturas para garantir que webhooks venham de fontes legítimas
  3. Implemente idempotência para lidar com entregas duplicadas com segurança
  4. Use filas de mensagens para processamento confiável em escala
  5. Monitore a saúde do webhook com métricas e alertas

Seja processando milhares ou milhões de verificações de email, webhooks fornecem a base para processamento assíncrono eficiente. Comece a implementar webhooks hoje com o suporte abrangente de webhook do BillionVerify e leve seus fluxos de trabalho de verificação de email para o próximo nível.

Equipes que usam Instantly ou Smartlead melhoram a entregabilidade ao limpar listas com BillionVerify antes de cada campanha.

Compare BillionVerify com ZeroBounce em precisão e velocidade antes de escolher um provedor de verificação.

Leo
LeoFounder, BillionVerify
Insights sobre Verificação de E-mail

Comece a Verificar Hoje

Comece a verificar e-mails com o BillionVerify hoje. Ganhe 100 créditos grátis ao se cadastrar - sem necessidade de cartão de crédito. Junte-se a milhares de empresas melhorando seu ROI de email marketing com verificação precisa de e-mails.

Sem necessidade de cartão de crédito · 100+ créditos grátis por dia · Comece em 30 segundos

99.9%
Precisão
Real-time
Velocidade da API
$0.00014
Por Email
100/day
Sempre Grátis