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.BILLIONVERIFY_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.BILLIONVERIFY_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.BILLIONVERIFY_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/billionverify',
['bulk.completed', 'bulk.failed', 'bulk.progress'],
process.env.BILLIONVERIFY_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/billionverify', async (req, res) => {
// Weryfikacja podpisu
const signature = req.headers['x-billionverify-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:
- Odpowiadaj szybko na żądania webhooków i przetwarzaj ładunki asynchronicznie
- Weryfikuj podpisy, aby upewnić się, że webhooki pochodzą z legalnych źródeł
- Zaimplementuj idempotentność, aby bezpiecznie obsługiwać duplikaty dostaw
- Używaj kolejek komunikatów do niezawodnego przetwarzania na dużą skalę
- 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.