Webhooki Weryfikacji E-mail: Przewodnik Asynchroniczny

Leo
LeoFounder, BillionVerify

Dowiedz się, jak wdrożyć webhooki weryfikacji e-mail. Przewodnik obejmujący konfigurację, bezpieczeństwo, obsługę błędów i wyniki masowej weryfikacji.

Cover Image for Webhooki Weryfikacji E-mail: Przewodnik Asynchroniczny

Gdy weryfikujesz tysiące lub miliony adresów e-mail, oczekiwanie synchroniczne na każdy wynik nie jest praktyczne. Webhooki weryfikacji e-mail zapewniają eleganckie rozwiązanie, powiadamiając Twoją aplikację o zakończeniu zadań weryfikacji, eliminując potrzebę ciągłego odpytywania i umożliwiając efektywne asynchroniczne przepływy pracy. Ten kompleksowy przewodnik omawia wszystko, co programiści muszą wiedzieć o wdrażaniu webhooków weryfikacji e-mail, od podstawowej konfiguracji po zaawansowane wzorce obsługi operacji weryfikacji na dużą skalę.

Zrozumienie webhooków weryfikacji e-mail

Webhooki to wywołania zwrotne HTTP, które dostarczają dane do Twojej aplikacji, gdy wystąpią określone zdarzenia. W kontekście weryfikacji e-mail webhooki powiadamiają Twoje systemy o zakończeniu zadań masowej weryfikacji, zakończeniu weryfikacji pojedynczego e-maila w trybie asynchronicznym lub innych istotnych zdarzeniach podczas procesu weryfikacji.

Dlaczego używać webhooków do weryfikacji e-mail?

Tradycyjne wzorce żądanie-odpowiedź działają dobrze dla weryfikacji pojedynczych e-maili, ale operacje masowe stanowią wyzwanie. Weryfikacja 100 000 e-maili może zająć godziny, a utrzymywanie otwartego połączenia HTTP przez tak długi czas nie jest wykonalne. Odpytywanie o aktualizacje statusu marnuje zasoby i tworzy niepotrzebne obciążenie API.

Eliminacja nadmiarowego odpytywania

Bez webhooków musiałbyś wielokrotnie odpytywać API, aby sprawdzić, czy zadania masowe zostały zakończone. To tworzy niepotrzebny ruch sieciowy, zużywa limity API i dodaje złożoność do Twojej aplikacji. Webhooki wysyłają powiadomienia dokładnie wtedy, gdy są potrzebne.

Przetwarzanie w czasie rzeczywistym

Webhooki umożliwiają natychmiastowe działanie po zakończeniu weryfikacji. Twoja aplikacja może przetwarzać wyniki, aktualizować bazy danych i uruchamiać działania następcze bez opóźnień wprowadzanych przez interwały odpytywania.

Skalowalna architektura

Architektury oparte na webhookach skalują się naturalnie. Niezależnie od tego, czy przetwarzasz jedno zadanie masowe, czy setki jednocześnie, Twój endpoint webhookowy otrzymuje powiadomienia w miarę ich nadejścia i możesz je przetwarzać asynchronicznie za pomocą kolejek lub workerów.

Efektywność zasobów

Zamiast utrzymywać połączenia lub uruchamiać pętle odpytywania, Twoja aplikacja pozostaje bezczynna do momentu nadejścia webhooków. To redukuje koszty obliczeniowe i upraszcza wymagania infrastrukturalne.

Zdarzenia webhooków w weryfikacji e-mail

Usługi weryfikacji e-mail zazwyczaj wyzwalają webhooki dla kilku typów zdarzeń:

Zakończenie zadania masowego

Najczęstsze zdarzenie webhookowe jest wyzwalane, gdy zadanie masowej weryfikacji kończy przetwarzanie. Ładunek zawiera status zadania, statystyki podsumowujące i informacje o pobieraniu wyników.

Postęp zadania masowego

Niektóre usługi wysyłają webhooki postępu w interwałach podczas przetwarzania masowego, umożliwiając śledzenie postępu weryfikacji i szacowanie czasu zakończenia.

Niepowodzenie zadania masowego

Gdy zadanie masowe napotyka błędy, które uniemożliwiają zakończenie, webhooki niepowodzenia dostarczają szczegóły dotyczące tego, co poszło nie tak i czy dostępne są częściowe wyniki.

Weryfikacja pojedynczego e-maila (tryb asynchroniczny)

Dla scenariuszy weryfikacji w czasie rzeczywistym o dużej przepustowości, asynchroniczna weryfikacja pojedynczych e-maili wysyła wyniki przez webhook zamiast oczekiwać na synchroniczną odpowiedź.

Konfiguracja endpointów webhookowych

Implementacja webhooków wymaga utworzenia endpointu w Twojej aplikacji, który może odbierać i przetwarzać ładunki webhookowe.

Podstawowa struktura endpointu

Endpoint webhookowy to po prostu endpoint HTTP POST, który akceptuje ładunki 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(`Otrzymano webhook: ${event_type} dla zadania ${job_id}`);

  // Przetwarzanie webhooka
  try {
    await handleWebhookEvent(req.body);

    // Zawsze szybko odpowiadaj, aby potwierdzić odbiór
    res.status(200).json({ received: true });
  } catch (error) {
    console.error('Błąd przetwarzania webhooka:', error);

    // Nadal potwierdź odbiór, aby zapobiec ponownym próbom
    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(`Nieznany typ zdarzenia: ${payload.event_type}`);
  }
}

Najlepsze praktyki odpowiedzi webhookowych

Usługi weryfikacji e-mail oczekują szybkich odpowiedzi od endpointów webhookowych. Jeśli Twój endpoint zajmuje zbyt dużo czasu na odpowiedź, usługa może założyć, że dostawa się nie powiodła i ponowić próbę.

Odpowiadaj natychmiast

Potwierdź odbiór webhooka natychmiast, a następnie przetwarzaj ładunek asynchronicznie:

app.post('/webhooks/email-verification', async (req, res) => {
  // Natychmiast potwierdź odbiór
  res.status(200).json({ received: true });

  // Przetwarzaj asynchronicznie
  setImmediate(async () => {
    try {
      await handleWebhookEvent(req.body);
    } catch (error) {
      console.error('Błąd asynchronicznego przetwarzania webhooka:', error);
      // Zaloguj do ponownej próby lub ręcznego przetwarzania
      await logFailedWebhook(req.body, error);
    }
  });
});

Używaj kolejek komunikatów do ciężkiego przetwarzania

W systemach produkcyjnych umieszczaj ładunki webhookowe w kolejce do przetwarzania przez procesy workerów:

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

app.post('/webhooks/email-verification', async (req, res) => {
  // Umieść webhook w kolejce do przetwarzania
  await webhookQueue.add('process-webhook', req.body, {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 1000
    }
  });

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

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

Konfiguracja webhooków za pomocą API

Zarejestruj swój endpoint webhookowy w usłudze weryfikacji e-mail:

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(`Nie udało się zarejestrować webhooka: ${result.error}`);
  }

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

// Rejestracja dla zdarzeń zadań masowych
await registerWebhook(
  'https://yourapp.com/webhooks/email-verification',
  ['bulk.completed', 'bulk.failed', 'bulk.progress'],
  process.env.WEBHOOK_SECRET
);

Zabezpieczanie endpointów webhookowych

Endpointy webhookowe są publicznie dostępne, co sprawia, że bezpieczeństwo jest niezbędne. Bez odpowiedniej weryfikacji atakujący mogliby wysyłać fałszywe ładunki webhookowe w celu manipulowania Twoją aplikacją.

Weryfikacja podpisu

Większość usług weryfikacji e-mail podpisuje ładunki webhookowe używając HMAC-SHA256 z wspólnym sekretem. Weryfikuj podpisy przed przetwarzaniem:

const crypto = require('crypto');

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

  // Użyj porównania bezpiecznego czasowo, aby zapobiec atakom czasowym
  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: 'Brak podpisu' });
  }

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

  if (!isValid) {
    console.warn('Otrzymano nieprawidłowy podpis webhooka');
    return res.status(401).json({ error: 'Nieprawidłowy podpis' });
  }

  // Podpis ważny, przetwarzaj webhook
  await handleWebhookEvent(req.body);
  res.status(200).json({ received: true });
});

Walidacja znacznika czasowego

Zapobiegaj atakom typu replay poprzez walidację znaczników czasowych webhooków:

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('Znacznik czasowy webhooka poza akceptowalnym zakresem');
    return res.status(400).json({ error: 'Nieprawidłowy znacznik czasowy' });
  }

  // Kontynuuj weryfikację podpisu i przetwarzanie
});

Lista dozwolonych adresów IP

Dla dodatkowego bezpieczeństwa ogranicz dostęp do webhooków do znanych adresów IP:

const allowedIPs = [
  '203.0.113.0/24',  // Serwery webhookowe BillionVerify
  '198.51.100.0/24'
];

function isIPAllowed(clientIP) {
  // Implementacja sprawdzania zakresu CIDR
  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 z nieautoryzowanego IP: ${clientIP}`);
    return res.status(403).json({ error: 'Zabronione' });
  }

  // Kontynuuj przetwarzanie
});

Obsługa idempotentności

Webhooki mogą być dostarczane wielokrotnie z powodu problemów z siecią lub ponownych prób. Zaimplementuj idempotentność, aby bezpiecznie obsługiwać duplikaty:

const processedWebhooks = new Set(); // Użyj Redis w produkcji

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

  // Sprawdź, czy już przetworzono
  if (processedWebhooks.has(webhookId)) {
    console.log(`Zignorowano duplikat webhooka: ${webhookId}`);
    return;
  }

  // Oznacz jako przetwarzany
  processedWebhooks.add(webhookId);

  try {
    await handleWebhookEvent(payload);
  } catch (error) {
    // Usuń z zestawu przetworzonych, aby umożliwić ponowną próbę
    processedWebhooks.delete(webhookId);
    throw error;
  }
}

W systemach produkcyjnych użyj Redis dla rozproszonej idempotentności:

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); // Wygasa po 24 godzinach
  return result === null; // Już istnieje
}

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

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

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

Przetwarzanie ładunków webhookowych

Różne zdarzenia webhookowe wymagają różnej logiki obsługi. Przeanalizujmy typowe wzorce przetwarzania webhooków weryfikacji e-mail.

Obsługa zakończenia zadania masowego

Gdy zadanie masowej weryfikacji się zakończy, pobierz i przetwórz wyniki:

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

  console.log(`Zadanie masowe ${job_id} zakończone ze statusem: ${status}`);
  console.log(`Podsumowanie: ${summary.valid} ważnych, ${summary.invalid} nieważnych`);

  // Pobierz wyniki
  const results = await downloadResults(download_url);

  // Przetwarzaj wyniki
  await processVerificationResults(job_id, results);

  // Zaktualizuj status zadania w bazie danych
  await updateJobStatus(job_id, 'completed', summary);

  // Powiadom zainteresowane strony
  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(`Nie udało się pobrać wyników: ${response.status}`);
  }

  return await response.json();
}

async function processVerificationResults(jobId, results) {
  // Aktualizacja wsadowa kontaktów w bazie danych
  const validEmails = results.filter(r => r.is_valid);
  const invalidEmails = results.filter(r => !r.is_valid);

  await db.transaction(async (trx) => {
    // Zaktualizuj ważne e-maile
    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
        });
    }

    // Obsłuż nieważne e-maile
    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
        });
    }
  });
}

Obsługa niepowodzeń zadań masowych

Gdy zadania się nie powiodą, przechwyć informacje o błędach i określ, czy odzyskiwanie jest możliwe:

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

  console.error(`Zadanie masowe ${job_id} nie powiodło się: ${error_message}`);

  // Zaktualizuj status zadania
  await updateJobStatus(job_id, 'failed', {
    error_code,
    error_message
  });

  // Spróbuj pobrać częściowe wyniki, jeśli są dostępne
  if (partial_results_available) {
    console.log('Próba pobrania częściowych wyników...');
    try {
      const partialResults = await downloadPartialResults(job_id);
      await processVerificationResults(job_id, partialResults);

      // Zidentyfikuj nieprzetworzone e-maile do ponownej próby
      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) {
        // Zaplanuj ponowną próbę dla nieprzetworzonych e-maili
        await scheduleRetryJob(job_id, unprocessedEmails);
      }
    } catch (error) {
      console.error('Nie udało się pobrać częściowych wyników:', error);
    }
  }

  // Powiadom o niepowodzeniu
  await sendFailureNotification(job_id, error_message);
}

async function scheduleRetryJob(originalJobId, emails) {
  // Utwórz nowe zadanie dla pozostałych e-maili
  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(`Zaplanowano zadanie ponownej próby ${newJobId} dla ${emails.length} e-maili`);
}

Obsługa aktualizacji postępu

Webhooki postępu pomagają śledzić długotrwałe zadania:

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(`Zadanie ${job_id}: ${percentComplete}% ukończone (${processed_count}/${total_count})`);

  // Zaktualizuj postęp w bazie danych
  await updateJobProgress(job_id, {
    processed_count,
    total_count,
    percent_complete: percentComplete,
    estimated_completion: new Date(estimated_completion)
  });

  // Opcjonalnie powiadom użytkowników o postępie
  if (percentComplete % 25 === 0) {
    await sendProgressNotification(job_id, percentComplete);
  }
}

Zaawansowane wzorce webhookowe

Systemy produkcyjne korzystają z zaawansowanych wzorców, które poprawiają niezawodność i łatwość konserwacji.

Kolejka nieudanych wiadomości dla webhooków zakończonych niepowodzeniem

Gdy przetwarzanie webhooka wielokrotnie się nie powiedzie, przenieś ładunki do kolejki nieudanych wiadomości do ręcznego przeglądu:

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) {
    // Sprawdź, czy to końcowa ponowna próba
    if (job.attemptsMade >= job.opts.attempts - 1) {
      // Przenieś do kolejki nieudanych wiadomości
      await deadLetterQueue.add('failed-webhook', {
        original_payload: job.data,
        error: error.message,
        failed_at: new Date().toISOString(),
        attempts: job.attemptsMade + 1
      });
    }
    throw error; // Rzuć ponownie, aby wyzwolić ponowną próbę
  }
});

// Przetwarzaj nieudane wiadomości ręcznie lub z alertami
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 webhooków

Przechowuj wszystkie zdarzenia webhookowe do ścieżek audytu i możliwości odtwarzania:

async function handleWebhookWithEventSourcing(payload) {
  // Przechowaj surowe zdarzenie
  const eventId = await storeWebhookEvent(payload);

  try {
    // Przetwórz zdarzenie
    await handleWebhookEvent(payload);

    // Oznacz jako przetworzone
    await markEventProcessed(eventId);
  } catch (error) {
    // Oznacz jako nieudane
    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];
}

// Odtwórz nieudane zdarzenia
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);
    }
  }
}

Routing webhooków dla wielu najemców

Dla aplikacji SaaS kieruj webhooki do handlerów specyficznych dla najemcy:

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

  // Pobierz konfigurację najemcy
  const tenant = await getTenantConfig(tenant_id);

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

  // Kieruj do handlera specyficznego dla najemcy
  switch (event_type) {
    case 'bulk.completed':
      await handleTenantBulkCompleted(tenant, data);
      break;
    case 'bulk.failed':
      await handleTenantBulkFailed(tenant, data);
      break;
  }

  // Przekaż do webhooka najemcy, jeśli skonfigurowano
  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)
  });
}

Obsługa błędów i niezawodność

Solidne implementacje webhooków obsługują awarie z gracją i zapewniają, że żadne dane nie zostaną utracone.

Strategie ponownych prób

Zaimplementuj wykładnicze wycofywanie dla przejściowych awarii:

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; // Sukces
    } catch (error) {
      const isRetryable = isRetryableError(error);

      if (!isRetryable || attempt === maxRetries - 1) {
        // Zaloguj do kolejki nieudanych wiadomości
        await logFailedWebhook(payload, error, attempt + 1);
        throw error;
      }

      console.log(`Ponowna próba ${attempt + 1}/${maxRetries} po ${delays[attempt]}ms`);
      await sleep(delays[attempt]);
    }
  }
}

function isRetryableError(error) {
  // Błędy sieciowe, limity czasu i odpowiedzi 5xx można ponowić
  const retryableCodes = ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND'];
  return retryableCodes.includes(error.code) ||
         (error.status && error.status >= 500);
}

Wzorzec wyłącznika automatycznego

Zapobiegaj awariom kaskadowym, gdy usługi downstream są niedostępne:

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('Wyłącznik automatyczny jest OTWARTY');
      }
    }

    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('Wyłącznik automatyczny otwarty z powodu awarii');
    }
  }
}

const databaseCircuitBreaker = new CircuitBreaker();

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

Monitorowanie i alerty

Śledź metryki zdrowia webhooków:

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

    // Zachowaj tylko ostatnie 1000 pomiarów
    if (metrics.latency.length > 1000) {
      metrics.latency.shift();
    }
  }

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

// Udostępnij endpoint metryk
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 przy wysokiej częstotliwości niepowodzeń
setInterval(() => {
  const failureRate = metrics.failed / metrics.received;

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

Testowanie implementacji webhooków

Dokładne testowanie zapewnia, że handlery webhooków działają poprawnie w produkcji.

Testowanie lokalne za pomocą ngrok

Użyj ngrok do udostępnienia lokalnych endpointów do testowania webhooków:

# Uruchom swój lokalny serwer
node server.js

# W innym terminalu udostępnij go przez ngrok
ngrok http 3000

Zarejestruj URL ngrok jako swój endpoint webhookowy podczas tworzenia.

Makietowe ładunki webhookowe

Utwórz fikstury testowe dla różnych typów zdarzeń:

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: 'Błąd przetwarzania wewnętrznego',
    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()
  }
};

// Endpoint testowy
describe('Handler webhooków', () => {
  it('powinien przetwarzać zdarzenie bulk.completed', 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);

    // Weryfikuj efekty uboczne
    const job = await db('verification_jobs').where('job_id', 'job_123456').first();
    expect(job.status).toBe('completed');
  });
});

Testowanie integracyjne

Przetestuj pełny przepływ webhooka, włączając weryfikację podpisu:

describe('Bezpieczeństwo webhooków', () => {
  it('powinien odrzucać żądania bez podpisu', async () => {
    const response = await request(app)
      .post('/webhooks/email-verification')
      .send(mockPayloads.bulkCompleted);

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

  it('powinien odrzucać żądania z nieprawidłowym podpisem', 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('powinien akceptować żądania z prawidłowym podpisem', 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);
  });
});

Integracja webhooków BillionVerify

BillionVerify zapewnia kompleksowe wsparcie webhookowe dla zdarzeń weryfikacji e-mail, ułatwiając budowanie asynchronicznych przepływów pracy weryfikacji.

Konfiguracja webhooków

Skonfiguruj webhooki za pomocą panelu BillionVerify lub API:

// Rejestracja webhooka przez 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 skonfigurowany:', webhook);
}

Format ładunku webhooka

Webhooki BillionVerify zawierają kompleksowe informacje o zdarzeniach weryfikacji:

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

Kompletny przykład integracji

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

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

// Endpoint webhookowy dla BillionVerify
app.post('/webhooks/emailverify', async (req, res) => {
  // Weryfikacja podpisu
  const signature = req.headers['x-emailverify-signature'];
  const isValid = verifySignature(req.body, signature);

  if (!isValid) {
    return res.status(401).json({ error: 'Nieprawidłowy podpis' });
  }

  // Potwierdź natychmiast
  res.status(200).json({ received: true });

  // Przetwarzaj asynchronicznie
  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('Błąd przetwarzania webhooka:', error);
    await logFailedWebhook(payload, error);
  }
}

app.listen(3000, () => {
  console.log('Serwer webhooków działa na porcie 3000');
});

Podsumowanie

Webhooki weryfikacji e-mail przekształcają sposób, w jaki aplikacje obsługują weryfikację masową, umożliwiając wydajne, skalowalne i niezawodne przetwarzanie asynchroniczne. Implementując odpowiednią obsługę webhooków z środkami bezpieczeństwa, obsługą błędów i monitorowaniem, możesz budować solidne przepływy pracy weryfikacji e-mail, które skalują się wraz z potrzebami Twojej aplikacji.

Kluczowe wnioski dotyczące implementacji webhooków weryfikacji e-mail:

  1. Odpowiadaj szybko na żądania webhooków i przetwarzaj ładunki asynchronicznie
  2. Weryfikuj podpisy, aby upewnić się, że webhooki pochodzą z legalnych źródeł
  3. Zaimplementuj idempotentność, aby bezpiecznie obsługiwać duplikaty dostaw
  4. Używaj kolejek komunikatów do niezawodnego przetwarzania na dużą skalę
  5. Monitoruj zdrowie webhooków za pomocą metryk i alertów

Niezależnie od tego, czy przetwarzasz tysiące, czy miliony weryfikacji e-mail, webhooki zapewniają podstawę do wydajnego przetwarzania asynchronicznego. Zacznij wdrażać webhooki już dziś dzięki kompleksowemu wsparciu webhookowemu BillionVerify i przenieś swoje przepływy pracy weryfikacji e-mail na wyższy poziom.

Zespoły korzystające z Instantly lub Smartlead poprawiają dostarczalność, czyszcząc listy z BillionVerify przed każdą kampanią.

Porównaj BillionVerify z ZeroBounce pod kątem dokładności i szybkości przed wyborem dostawcy weryfikacji.

Leo
LeoFounder, BillionVerify
Informacje o weryfikacji e-mail

Rozpocznij weryfikację dzisiaj

Zacznij weryfikować adresy e-mail z BillionVerify już dziś. Otrzymaj 100 darmowych kredytów po rejestracji - nie wymagana karta kredytowa. Dołącz do tysięcy firm poprawiających ROI z marketingu e-mailowego dzięki dokładnej weryfikacji e-mail.

Nie wymagana karta kredytowa · 100+ darmowych kredytów dziennie · Rozpocznij w 30 sekund

99.9%
Dokładność
Real-time
Szybkość API
$0.00014
Za e-mail
100/day
Darmowe na zawsze