Erken erişim talep edin

Adınızı ve e-posta adresinizi veya telefon numaranızı paylaşın. Tajo erişim ayrıntıları için sizinle iletişime geçeceğiz.

Webhook kurulum rehberi

Webhook’lar, Brevo ile Tajo sadakat platformunuz arasında gerçek zamanlı iletişim sağlar. Bu rehber, kurulum sürecinin tamamında size yol gösterir.

Genel bakış

Webhook’lar, belirli olaylar gerçekleştiğinde Brevo’nun Tajo uygulamanızı otomatik olarak bilgilendirmesini sağlar. Örneğin:

  • E-posta olayları: Teslim edildi, açıldı, tıklandı, geri döndü
  • SMS olayları: Gönderildi, teslim edildi, başarısız oldu, yanıtlandı
  • Kişi olayları: Oluşturuldu, güncellendi, abonelikten çıkıldı
  • Kampanya olayları: Başlatıldı, tamamlandı, duraklatıldı

Ön koşullar

Webhook’ları kurmadan önce şunlara sahip olduğunuzdan emin olun:

  • HTTPS uç noktası: webhook almak için (SSL zorunlu)
  • Webhook gizli anahtarı: imza doğrulaması için
  • Sunucu ortamı: HTTP POST isteklerini işleyebilen
  • Brevo hesabı: webhook erişim izinlerine sahip

Adım 1: Webhook uç noktanızı hazırlayın

Webhook işleyicisi oluşturma

import express from 'express';
import crypto from 'crypto';
import { TajoLoyaltyService } from './loyalty-service.js';
const app = express();
const loyaltyService = new TajoLoyaltyService();
// Middleware to capture raw body for signature verification
app.use('/webhooks/brevo', express.raw({
type: 'application/json',
limit: '10mb'
}));
// Main webhook handler
app.post('/webhooks/brevo', async (req, res) => {
try {
// Verify webhook signature
const signature = req.headers['x-brevo-signature'];
if (!verifyWebhookSignature(req.body, signature)) {
console.warn('Invalid webhook signature received');
return res.status(401).json({ error: 'Unauthorized: Invalid signature' });
}
// Parse webhook payload
const event = JSON.parse(req.body.toString());
console.log('Received webhook event:', event.event, event.email);
// Route to appropriate handler
await handleWebhookEvent(event);
// Respond quickly (Brevo expects response within 5 seconds)
res.status(200).json({
success: true,
eventId: event['message-id'],
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
// Signature verification function
function verifyWebhookSignature(payload, signature) {
if (!process.env.BREVO_WEBHOOK_SECRET || !signature) {
return false;
}
const expectedSignature = crypto
.createHmac('sha256', process.env.BREVO_WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', ''), 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Event router
async function handleWebhookEvent(event) {
switch (event.event) {
// Email events
case 'delivered':
await handleEmailDelivered(event);
break;
case 'opened':
await handleEmailOpened(event);
break;
case 'clicked':
await handleEmailClicked(event);
break;
case 'bounced':
case 'hard_bounced':
await handleEmailBounced(event);
break;
case 'spam':
await handleEmailSpam(event);
break;
case 'unsubscribed':
await handleEmailUnsubscribed(event);
break;
// SMS events
case 'sms_delivered':
await handleSMSDelivered(event);
break;
case 'sms_failed':
await handleSMSFailed(event);
break;
case 'sms_reply':
await handleSMSReply(event);
break;
// Contact events
case 'contact_created':
await handleContactCreated(event);
break;
case 'contact_updated':
await handleContactUpdated(event);
break;
case 'list_addition':
await handleListAddition(event);
break;
default:
console.warn('Unhandled webhook event:', event.event);
}
}

E-posta olayı işleyicileri

// Handle email delivery confirmation
async function handleEmailDelivered(event) {
const customerEmail = event.email;
const messageId = event['message-id'];
await loyaltyService.updateCustomerEngagement(customerEmail, {
lastEmailDelivered: new Date(),
emailDeliveryRate: 'increment'
});
// Log for analytics
console.log(`Email delivered to ${customerEmail}: ${messageId}`);
}
// Handle email opens (key engagement metric)
async function handleEmailOpened(event) {
const customerEmail = event.email;
const subject = event.subject;
const timestamp = new Date(event.ts * 1000);
// Update customer engagement score
await loyaltyService.updateCustomerEngagement(customerEmail, {
lastEmailOpened: timestamp,
emailOpenRate: 'increment',
engagementScore: 'increase'
});
// Track loyalty campaign engagement
if (event.tag?.includes('loyalty')) {
await loyaltyService.trackLoyaltyEngagement(customerEmail, {
event: 'email_opened',
campaign: extractCampaignFromSubject(subject),
timestamp: timestamp
});
}
console.log(`Email opened by ${customerEmail}: "${subject}"`);
}
// Handle email clicks (high-value engagement)
async function handleEmailClicked(event) {
const customerEmail = event.email;
const clickedUrl = event.link;
const timestamp = new Date(event.ts * 1000);
// High-value engagement - boost customer score
await loyaltyService.updateCustomerEngagement(customerEmail, {
lastEmailClicked: timestamp,
emailClickRate: 'increment',
engagementScore: 'boost'
});
// Track reward page visits
if (clickedUrl.includes('/rewards') || clickedUrl.includes('/loyalty')) {
await loyaltyService.trackEvent(customerEmail, 'Rewards Page Visited', {
source: 'email',
referrer_url: clickedUrl,
timestamp: timestamp
});
}
console.log(`Email link clicked by ${customerEmail}: ${clickedUrl}`);
}
// Handle email bounces (delivery issues)
async function handleEmailBounced(event) {
const customerEmail = event.email;
const bounceReason = event.reason;
const bounceType = event.event; // 'bounced' or 'hard_bounced'
if (bounceType === 'hard_bounced') {
// Hard bounce - email address invalid
await loyaltyService.updateCustomerStatus(customerEmail, {
emailStatus: 'invalid',
emailBounced: true,
bounceReason: bounceReason,
lastBounce: new Date()
});
// Consider switching to SMS for critical notifications
await loyaltyService.suggestAlternativeChannel(customerEmail, 'sms');
} else {
// Soft bounce - temporary issue
await loyaltyService.updateCustomerEngagement(customerEmail, {
emailBounceCount: 'increment',
lastBounce: new Date()
});
}
console.warn(`Email bounced for ${customerEmail}: ${bounceReason}`);
}
// Handle spam reports (reputation management)
async function handleEmailSpam(event) {
const customerEmail = event.email;
// Mark customer as unengaged to prevent future spam reports
await loyaltyService.updateCustomerStatus(customerEmail, {
emailStatus: 'spam_reported',
marketingEnabled: false,
lastSpamReport: new Date()
});
// Alert marketing team for review
await loyaltyService.alertMarketing('spam_report', {
email: customerEmail,
campaign: event.tag
});
console.warn(`Spam reported by ${customerEmail}`);
}

SMS olayı işleyicileri

// Handle SMS delivery confirmation
async function handleSMSDelivered(event) {
const customerPhone = event.phone;
const messageId = event['message-id'];
await loyaltyService.updateCustomerEngagement(customerPhone, {
lastSMSDelivered: new Date(),
smsDeliveryRate: 'increment'
});
console.log(`SMS delivered to ${customerPhone}: ${messageId}`);
}
// Handle SMS failures
async function handleSMSFailed(event) {
const customerPhone = event.phone;
const failureReason = event.reason;
await loyaltyService.updateCustomerStatus(customerPhone, {
smsStatus: 'failed',
smsFailureReason: failureReason,
lastSMSFailure: new Date()
});
// If SMS fails, consider email as alternative
const customer = await loyaltyService.getCustomerByPhone(customerPhone);
if (customer?.email) {
await loyaltyService.suggestAlternativeChannel(customer.email, 'email');
}
console.warn(`SMS failed for ${customerPhone}: ${failureReason}`);
}
// Handle SMS replies (two-way communication)
async function handleSMSReply(event) {
const customerPhone = event.phone;
const replyText = event.text.toLowerCase().trim();
// Process common replies
if (replyText === 'stop' || replyText === 'unsubscribe') {
await loyaltyService.unsubscribeFromSMS(customerPhone);
} else if (replyText === 'help' || replyText === 'info') {
await loyaltyService.sendSMSHelp(customerPhone);
} else {
// Forward to customer service
await loyaltyService.forwardSMSToSupport(customerPhone, replyText);
}
console.log(`SMS reply from ${customerPhone}: "${replyText}"`);
}

Adım 2: Brevo’da webhook’u yapılandırın

Brevo yönetim paneli üzerinden

  1. Brevo hesabınıza giriş yapın
  2. Developers > Webhooks bölümüne gidin
  3. “Add a new webhook” seçeneğine tıklayın
  4. Webhook ayarlarını yapılandırın:
{
"url": "https://your-tajo-domain.com/webhooks/brevo",
"description": "Tajo Loyalty Platform Integration",
"events": [
"delivered",
"opened",
"clicked",
"bounced",
"hard_bounced",
"spam",
"unsubscribed",
"sms_delivered",
"sms_failed",
"sms_reply",
"contact_created",
"contact_updated"
]
}

API üzerinden

import { WebhooksApi } from '@getbrevo/brevo';
async function createWebhook() {
const webhooksApi = new WebhooksApi();
const createWebhook = {
url: 'https://your-tajo-domain.com/webhooks/brevo',
description: 'Tajo Loyalty Platform Integration',
events: [
'delivered',
'opened',
'clicked',
'bounced',
'hard_bounced',
'spam',
'unsubscribed',
'sms_delivered',
'sms_failed',
'sms_reply',
'contact_created',
'contact_updated'
]
};
try {
const response = await webhooksApi.createWebhook(createWebhook);
console.log('Webhook created successfully:', response.id);
return response;
} catch (error) {
console.error('Error creating webhook:', error);
throw error;
}
}

Adım 3: Güvenliğin uygulanması

Ortam değişkenleri

Terminal window
# .env file
BREVO_WEBHOOK_SECRET=your-super-secure-webhook-secret-here
BREVO_API_KEY=xkeysib-your-api-key-here
WEBHOOK_RATE_LIMIT=1000
WEBHOOK_TIMEOUT=5000

Hız sınırlama

import rateLimit from 'express-rate-limit';
const webhookLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 1000, // Limit each IP to 1000 requests per windowMs
message: 'Too many webhook requests from this IP',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/webhooks/brevo', webhookLimiter);

IP beyaz listesi (isteğe bağlı)

const brevoIPs = [
'185.41.28.0/24',
'185.41.29.0/24',
'217.182.196.0/24'
];
function isBrevoIP(ip) {
// Implement IP range checking
return brevoIPs.some(range => ipInRange(ip, range));
}
app.use('/webhooks/brevo', (req, res, next) => {
const clientIP = req.ip || req.connection.remoteAddress;
if (process.env.NODE_ENV === 'production' && !isBrevoIP(clientIP)) {
return res.status(403).json({ error: 'Forbidden: Invalid source IP' });
}
next();
});

Adım 4: Webhook’ları test etme

Test webhook işleyicisi

// Test endpoint for webhook verification
app.post('/webhooks/brevo/test', (req, res) => {
const testEvent = {
event: 'test',
'message-id': 'test-message-id',
timestamp: Date.now() / 1000,
tags: ['test', 'loyalty']
};
console.log('Test webhook received:', testEvent);
res.status(200).json({
success: true,
message: 'Test webhook processed successfully',
receivedAt: new Date().toISOString()
});
});

Manuel test

Terminal window
# Test webhook endpoint with curl
curl -X POST https://your-domain.com/webhooks/brevo/test \
-H "Content-Type: application/json" \
-H "X-Brevo-Signature: sha256=test-signature" \
-d '{
"event": "delivered",
"email": "[email protected]",
"message-id": "test-123",
"ts": 1640995200
}'

Webhook doğrulama

class WebhookValidator {
static validateEvent(event) {
const required = ['event', 'email', 'message-id'];
const missing = required.filter(field => !event[field]);
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(', ')}`);
}
// Validate email format
if (!this.isValidEmail(event.email)) {
throw new Error('Invalid email format');
}
// Validate event type
const validEvents = [
'delivered', 'opened', 'clicked', 'bounced', 'hard_bounced',
'spam', 'unsubscribed', 'sms_delivered', 'sms_failed'
];
if (!validEvents.includes(event.event)) {
throw new Error(`Invalid event type: ${event.event}`);
}
return true;
}
static isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}

Adım 5: İzleme ve günlük kaydı

Webhook izleme

import winston from 'winston';
const webhookLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'webhook-error.log', level: 'error' }),
new winston.transports.File({ filename: 'webhook-combined.log' })
]
});
// Add monitoring middleware
app.use('/webhooks/brevo', (req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
webhookLogger.info('Webhook processed', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: duration,
userAgent: req.headers['user-agent'],
contentLength: req.headers['content-length']
});
});
next();
});

Sağlık kontrolü uç noktası

app.get('/webhooks/brevo/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
version: process.env.npm_package_version,
environment: process.env.NODE_ENV,
checks: {
database: await checkDatabaseConnection(),
redis: await checkRedisConnection(),
brevoAPI: await checkBrevoAPIConnection()
}
};
const allHealthy = Object.values(health.checks).every(check => check.status === 'ok');
res.status(allHealthy ? 200 : 503).json(health);
});

Adım 6: Hata yönetimi ve kurtarma

Yeniden deneme mantığı

class WebhookProcessor {
constructor() {
this.maxRetries = 3;
this.retryDelay = 1000; // 1 second
}
async processEvent(event, retries = 0) {
try {
await this.handleEvent(event);
} catch (error) {
if (retries < this.maxRetries && this.isRetryableError(error)) {
console.warn(`Webhook processing failed, retrying... (${retries + 1}/${this.maxRetries})`);
await this.delay(this.retryDelay * Math.pow(2, retries)); // Exponential backoff
return this.processEvent(event, retries + 1);
}
// Max retries reached or non-retryable error
await this.handleFailedEvent(event, error);
throw error;
}
}
isRetryableError(error) {
// Retry on temporary failures
return error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT' ||
(error.status >= 500 && error.status < 600);
}
async handleFailedEvent(event, error) {
// Store failed event for manual review
await loyaltyService.storeFailedEvent(event, error.message);
// Alert operations team for critical events
if (this.isCriticalEvent(event)) {
await loyaltyService.alertOps('webhook_failure', {
event: event.event,
email: event.email,
error: error.message
});
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

Ölü mektup kuyruğu

import Bull from 'bull';
const webhookQueue = new Bull('webhook processing', process.env.REDIS_URL);
const deadLetterQueue = new Bull('webhook failed', process.env.REDIS_URL);
webhookQueue.process(async (job) => {
const { event } = job.data;
await handleWebhookEvent(event);
});
webhookQueue.on('failed', async (job, err) => {
console.error(`Webhook job failed: ${err.message}`);
// Add to dead letter queue for manual processing
await deadLetterQueue.add('failed webhook', {
originalEvent: job.data.event,
error: err.message,
failedAt: new Date(),
attempts: job.attemptsMade
});
});

Sık karşılaşılan sorunların giderilmesi

1. İmza doğrulaması başarısız oluyor

// Debug signature verification
function debugSignature(payload, receivedSignature) {
const expectedSignature = crypto
.createHmac('sha256', process.env.BREVO_WEBHOOK_SECRET)
.update(payload)
.digest('hex');
console.log('Received signature:', receivedSignature);
console.log('Expected signature:', expectedSignature);
console.log('Payload length:', payload.length);
console.log('First 100 chars:', payload.slice(0, 100));
return expectedSignature === receivedSignature.replace('sha256=', '');
}

2. Eksik olaylar

Webhook yapılandırmasını kontrol edin:

async function auditWebhookConfig() {
const webhooksApi = new WebhooksApi();
try {
const webhooks = await webhooksApi.getWebhooks();
webhooks.webhooks.forEach(webhook => {
console.log('Webhook ID:', webhook.id);
console.log('URL:', webhook.url);
console.log('Events:', webhook.events);
console.log('Status:', webhook.is_enabled ? 'enabled' : 'disabled');
});
} catch (error) {
console.error('Error auditing webhooks:', error);
}
}

3. Yüksek gecikme

Webhook işlemeyi optimize edin:

// Process webhooks asynchronously
app.post('/webhooks/brevo', async (req, res) => {
// Verify signature quickly
if (!verifyWebhookSignature(req.body, req.headers['x-brevo-signature'])) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Respond immediately
res.status(200).json({ success: true });
// Process event asynchronously
const event = JSON.parse(req.body.toString());
webhookQueue.add('process event', { event }, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
});
});

Sonraki adımlar

Erken erişim talep edin

Adınızı ve e-posta adresinizi veya telefon numaranızı paylaşın. Tajo erişim ayrıntıları için sizinle iletişime geçeceğiz.

otomatik algılama
AI Asistan

Merhaba! Belgeler hakkında her şeyi sorabilirsiniz.