Webhooks de Verificación Email: Procesamiento Asíncrono

Leo
LeoFounder, BillionVerify

Aprende a implementar webhooks de verificación de email. Guía de configuración, seguridad, manejo de errores y mejores prácticas.

Cover Image for Webhooks de Verificación Email: Procesamiento Asíncrono

Cuando está verificando miles o millones de direcciones de email, esperar sincrónicamente por cada resultado no es práctico. Los webhooks de verificación de email proporcionan una solución elegante al notificar a su aplicación cuando las tareas de verificación se completan, eliminando la necesidad de polling constante y permitiendo flujos de trabajo asíncronos eficientes. Esta guía completa explora todo lo que los desarrolladores necesitan saber sobre la implementación de webhooks de verificación de email, desde la configuración básica hasta patrones avanzados para manejar operaciones de verificación a gran escala.

Comprendiendo los Webhooks de Verificación de Email

Los webhooks son callbacks HTTP que entregan datos a su aplicación cuando ocurren eventos específicos. En el contexto de verificación de email, los webhooks notifican a sus sistemas cuando se completan trabajos de verificación masiva, cuando finaliza la verificación de un email individual en modo asíncrono, o cuando ocurren otros eventos significativos durante el proceso de verificación.

¿Por Qué Usar Webhooks para Verificación de Email?

Los patrones tradicionales de solicitud-respuesta funcionan bien para verificación de email individual, pero las operaciones masivas presentan desafíos. Verificar 100,000 emails podría tomar horas, y mantener una conexión HTTP abierta durante tanto tiempo no es factible. El polling para obtener actualizaciones de estado desperdicia recursos y crea carga innecesaria en la API.

Eliminación de Sobrecarga de Polling

Sin webhooks, necesitaría consultar repetidamente la API para verificar si los trabajos masivos se han completado. Esto crea tráfico de red innecesario, consume límites de tasa de API y añade complejidad a su aplicación. Los webhooks envían notificaciones exactamente cuando son necesarias.

Procesamiento en Tiempo Real

Los webhooks permiten acción inmediata cuando se completa la verificación. Su aplicación puede procesar resultados, actualizar bases de datos y disparar acciones de seguimiento sin retrasos introducidos por intervalos de polling.

Arquitectura Escalable

Las arquitecturas basadas en webhooks escalan naturalmente. Ya sea que esté procesando un trabajo masivo o cientos simultáneamente, su endpoint de webhook recibe notificaciones a medida que llegan, y puede procesarlas de forma asíncrona usando colas o workers.

Eficiencia de Recursos

En lugar de mantener conexiones o ejecutar bucles de polling, su aplicación permanece inactiva hasta que llegan los webhooks. Esto reduce costos de cómputo y simplifica los requisitos de infraestructura.

Eventos de Webhook en Verificación de Email

Los servicios de verificación de email típicamente activan webhooks para varios tipos de eventos:

Finalización de Trabajo Masivo

El evento de webhook más común se dispara cuando finaliza el procesamiento de un trabajo de verificación masiva. La carga útil incluye estado del trabajo, estadísticas resumidas e información sobre la descarga de resultados.

Progreso de Trabajo Masivo

Algunos servicios envían webhooks de progreso a intervalos durante el procesamiento masivo, permitiéndole rastrear el progreso de verificación y estimar el tiempo de finalización.

Fallo de Trabajo Masivo

Cuando un trabajo masivo encuentra errores que impiden su finalización, los webhooks de fallo proporcionan detalles sobre qué salió mal y si hay resultados parciales disponibles.

Verificación de Email Individual (Modo Asíncrono)

Para escenarios de verificación en tiempo real de alto volumen, la verificación asíncrona de email individual envía resultados vía webhook en lugar de esperar por respuesta síncrona.

Configurando Endpoints de Webhook

Implementar webhooks requiere crear un endpoint en su aplicación que pueda recibir y procesar cargas útiles de webhook.

Estructura Básica de Endpoint

Un endpoint de webhook es simplemente un endpoint HTTP POST que acepta cargas útiles 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}`);
  }
}

Mejores Prácticas de Respuesta de Webhook

Los servicios de verificación de email esperan respuestas rápidas de los endpoints de webhook. Si su endpoint tarda demasiado en responder, el servicio puede asumir que la entrega falló y reintentar.

Responder Inmediatamente

Reconozca la recepción del webhook inmediatamente, luego procese la carga útil de forma así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);
    }
  });
});

Usar Colas de Mensajes para Procesamiento Pesado

Para sistemas en producción, ponga en cola las cargas útiles de webhook para procesamiento por procesos 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 con la API

Registre su endpoint de webhook con el servicio de verificación 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
);

Asegurando Endpoints de Webhook

Los endpoints de webhook son públicamente accesibles, haciendo la seguridad esencial. Sin verificación adecuada, los atacantes podrían enviar cargas útiles de webhook falsas para manipular su aplicación.

Verificación de Firma

La mayoría de los servicios de verificación de email firman las cargas útiles de webhook usando HMAC-SHA256 con un secreto compartido. Verifique las firmas antes de procesar:

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 });
});

Validación de Marca de Tiempo

Prevenga ataques de replay validando las marcas de tiempo 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 IPs Permitidas

Para seguridad adicional, restrinja el acceso de webhook a direcciones IP conocidas:

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
});

Manejo de Idempotencia

Los webhooks pueden entregarse múltiples veces debido a problemas de red o reintentos. Implemente idempotencia para manejar duplicados 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 en producción, use Redis para idempotencia distribuida:

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 });
});

Procesando Cargas Útiles de Webhook

Diferentes eventos de webhook requieren diferente lógica de manejo. Exploremos patrones comunes para procesar webhooks de verificación de email.

Manejando Finalización de Trabajo Masivo

Cuando un trabajo de verificación masiva se completa, descargue y procese los 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
        });
    }
  });
}

Manejando Fallos de Trabajo Masivo

Cuando los trabajos fallan, capture la información de error y determine si la recuperación es posible:

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`);
}

Manejando Actualizaciones de Progreso

Los webhooks de progreso ayudan a rastrear trabajos de larga duración:

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);
  }
}

Patrones Avanzados de Webhook

Los sistemas en producción se benefician de patrones avanzados que mejoran la confiabilidad y mantenibilidad.

Cola de Mensajes No Entregables para Webhooks Fallidos

Cuando el procesamiento de webhook falla repetidamente, mueva las cargas útiles a una cola de mensajes no entregables para revisión 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

Almacene todos los eventos de webhook para rastros de auditoría y capacidad de reproducción:

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);
    }
  }
}

Enrutamiento de Webhook Multi-Tenant

Para aplicaciones SaaS, enrute webhooks a manejadores específicos del 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)
  });
}

Manejo de Errores y Confiabilidad

Las implementaciones robustas de webhook manejan fallos con gracia y aseguran que no se pierdan datos.

Estrategias de Reintento

Implemente backoff exponencial para fallos transitorios:

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);
}

Patrón Circuit Breaker

Prevenga fallos en cascada cuando los servicios downstream no estén disponibles:

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);
  });
}

Monitoreo y Alertas

Rastree métricas de salud de 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);

Probando Implementaciones de Webhook

Las pruebas exhaustivas aseguran que los manejadores de webhook funcionen correctamente en producción.

Pruebas Locales con ngrok

Use ngrok para exponer endpoints locales para pruebas de webhook:

# Start your local server
node server.js

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

Registre la URL de ngrok como su endpoint de webhook durante el desarrollo.

Cargas Útiles de Webhook Simuladas

Cree fixtures de prueba 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');
  });
});

Pruebas de Integración

Pruebe el flujo completo de webhook incluyendo verificación de firma:

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);
  });
});

Integración de Webhook con BillionVerify

BillionVerify proporciona soporte integral de webhook para eventos de verificación de email, facilitando la construcción de flujos de trabajo de verificación asíncronos.

Configurando Webhooks

Configure webhooks a través del dashboard de BillionVerify o la 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 de Carga Útil de Webhook

Los webhooks de BillionVerify incluyen información completa sobre eventos de verificación:

{
  "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"
}

Ejemplo de Integración 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');
});

Conclusión

Los webhooks de verificación de email transforman cómo las aplicaciones manejan la verificación masiva al permitir procesamiento asíncrono eficiente, escalable y confiable. Al implementar manejo adecuado de webhooks con medidas de seguridad, manejo de errores y monitoreo, puede construir flujos de trabajo de verificación de email robustos que escalen con las necesidades de su aplicación.

Conclusiones clave para implementar webhooks de verificación de email:

  1. Responda rápidamente a las solicitudes de webhook y procese cargas útiles de forma asíncrona
  2. Verifique firmas para asegurar que los webhooks provienen de fuentes legítimas
  3. Implemente idempotencia para manejar entregas duplicadas de forma segura
  4. Use colas de mensajes para procesamiento confiable a escala
  5. Monitoree la salud de webhook con métricas y alertas

Ya sea que esté procesando miles o millones de verificaciones de email, los webhooks proporcionan la base para un procesamiento asíncrono eficiente. Comience a implementar webhooks hoy con el soporte integral de webhooks de BillionVerify y lleve sus flujos de trabajo de verificación de email al siguiente nivel.

Los equipos que usan Instantly o Smartlead mejoran su tasa de entrega al limpiar listas con BillionVerify antes de cada campaña.

Compara BillionVerify con ZeroBounce en precisión y velocidad antes de elegir una herramienta de verificación.

Leo
LeoFounder, BillionVerify
Información sobre verificación de correo electrónico

Comience a verificar hoy

Empieza a verificar correos electrónicos con BillionVerify hoy mismo. Obtén 100 créditos gratis al registrarte (sin necesidad de tarjeta de crédito). Únete a miles de empresas que mejoran el retorno de la inversión (ROI) de su email marketing con una verificación precisa.

No se requiere tarjeta de crédito · 100+ créditos gratis diarios · Comienza en 30 segundos

99.9%
Precisión
Real-time
Velocidad de API
$0.00014
Por correo
100/day
Gratis para siempre