WhatsApp API untuk Broadcast & Bulk Message

Panduan broadcast dan bulk message dengan WhatsApp API. Rate limiting, best practices, avoid ban. Tutorial lengkap developer!

WhatsApp API untuk Broadcast & Bulk Message
WhatsApp API untuk Broadcast & Bulk Message

Bulk message perlu strategi!

Kirim pesan ke banyak orang via WhatsApp API perlu memperhatikan rate limit, template rules, dan anti-spam untuk menghindari ban.


Official vs Unofficial

📊 PERBANDINGAN:

OFFICIAL (Cloud API):
✅ Legal & aman
✅ Template-based
✅ High throughput
❌ Harus pakai HSM templates
❌ Biaya per conversation

UNOFFICIAL (Library):
⚠️ Risiko ban
⚠️ Rate limit ketat
✅ Flexible content
✅ Gratis
❌ Tidak stabil

Rate Limits

Official API:

📊 RATE LIMIT OFFICIAL:

MESSAGING LIMITS (per 24 jam):
- Tier 1: 1,000 business-initiated
- Tier 2: 10,000 business-initiated
- Tier 3: 100,000 business-initiated
- Tier 4: Unlimited

MESSAGES PER SECOND:
- Standard: 80 messages/second
- High throughput: 250+ msg/second

CARA NAIK TIER:
- Maintain quality rating
- Tidak banyak block/report
- Volume konsisten

Unofficial API:

⚠️ RATE LIMIT UNOFFICIAL:

RECOMMENDED SAFE:
- 1 pesan per 3-5 detik
- Max 200-300 pesan per hari
- Jeda 1 jam setiap 50 pesan

RISIKO BAN:
- Terlalu cepat = temporary ban
- Banyak report = permanent ban
- Konten spam = instant ban

Implementasi Broadcast

Official API Broadcast:

javascript

const axios = require('axios');

const PHONE_NUMBER_ID = process.env.PHONE_NUMBER_ID;
const ACCESS_TOKEN = process.env.ACCESS_TOKEN;

async function sendTemplateMessage(to, templateName, templateParams) {
    const url = `https://graph.facebook.com/v17.0/${PHONE_NUMBER_ID}/messages`;
    
    const payload = {
        messaging_product: 'whatsapp',
        to: to,
        type: 'template',
        template: {
            name: templateName,
            language: { code: 'id' },
            components: templateParams
        }
    };
    
    try {
        const response = await axios.post(url, payload, {
            headers: {
                'Authorization': `Bearer ${ACCESS_TOKEN}`,
                'Content-Type': 'application/json'
            }
        });
        
        return { success: true, messageId: response.data.messages[0].id };
    } catch (error) {
        return { 
            success: false, 
            error: error.response?.data?.error || error.message 
        };
    }
}

// Broadcast ke banyak nomor
async function broadcastTemplate(recipients, templateName, templateParams) {
    const results = [];
    
    for (const recipient of recipients) {
        const result = await sendTemplateMessage(
            recipient.phone, 
            templateName, 
            templateParams
        );
        
        results.push({
            phone: recipient.phone,
            ...result
        });
        
        // Rate limiting: 80/second = ~12ms delay
        await sleep(15);
    }
    
    return results;
}

Unofficial API Broadcast (Baileys):

javascript

const { default: makeWASocket } = require('@whiskeysockets/baileys');

async function broadcastMessage(sock, recipients, message) {
    const results = [];
    
    for (let i = 0; i < recipients.length; i++) {
        const recipient = recipients[i];
        const jid = `${recipient.phone}@s.whatsapp.net`;
        
        try {
            await sock.sendMessage(jid, { text: message });
            
            results.push({
                phone: recipient.phone,
                success: true
            });
            
            console.log(`[${i+1}/${recipients.length}] Sent to ${recipient.phone}`);
            
        } catch (error) {
            results.push({
                phone: recipient.phone,
                success: false,
                error: error.message
            });
        }
        
        // PENTING: Delay untuk avoid ban!
        // 3-5 detik antar pesan
        await sleep(3000 + Math.random() * 2000);
        
        // Jeda lebih lama setiap 50 pesan
        if ((i + 1) % 50 === 0) {
            console.log('Pausing for 5 minutes...');
            await sleep(5 * 60 * 1000);
        }
    }
    
    return results;
}

Queue System

Bull Queue untuk Broadcast:

javascript

const Queue = require('bull');
const broadcastQueue = new Queue('whatsapp-broadcast', {
    redis: { host: 'localhost', port: 6379 }
});

// Add job to queue
async function scheduleBroadcast(campaign) {
    for (const recipient of campaign.recipients) {
        await broadcastQueue.add({
            campaignId: campaign.id,
            phone: recipient.phone,
            templateName: campaign.templateName,
            templateParams: campaign.templateParams
        }, {
            delay: calculateDelay(recipient.index),
            attempts: 3,
            backoff: {
                type: 'exponential',
                delay: 60000
            }
        });
    }
}

// Process queue
broadcastQueue.process(async (job) => {
    const { phone, templateName, templateParams } = job.data;
    
    const result = await sendTemplateMessage(phone, templateName, templateParams);
    
    if (!result.success) {
        throw new Error(result.error);
    }
    
    return result;
});

// Track progress
broadcastQueue.on('completed', async (job, result) => {
    await updateCampaignProgress(job.data.campaignId, 'sent');
});

broadcastQueue.on('failed', async (job, error) => {
    await updateCampaignProgress(job.data.campaignId, 'failed', error.message);
});

Throttle Control:

javascript

const Bottleneck = require('bottleneck');

// Official API: 80/second = reservoir 80, minTime 12ms
const officialLimiter = new Bottleneck({
    reservoir: 80,
    reservoirRefreshAmount: 80,
    reservoirRefreshInterval: 1000, // per second
    minTime: 12
});

// Unofficial: 1 per 3 seconds, safer
const unofficialLimiter = new Bottleneck({
    minTime: 3000,
    maxConcurrent: 1
});

// Usage
async function sendWithLimit(phone, message) {
    return await officialLimiter.schedule(() => 
        sendTemplateMessage(phone, message)
    );
}

Personalisasi Broadcast

Template dengan Variables:

javascript

async function sendPersonalizedBroadcast(recipients) {
    for (const recipient of recipients) {
        // Personalize template params
        const templateParams = [
            {
                type: 'body',
                parameters: [
                    { type: 'text', text: recipient.name },
                    { type: 'text', text: recipient.lastProduct },
                    { type: 'text', text: recipient.discountCode }
                ]
            }
        ];
        
        await sendTemplateMessage(
            recipient.phone,
            'promo_personal',
            templateParams
        );
        
        await sleep(15);
    }
}

// Template di Meta:
// "Hai {{1}}! Produk {{2}} favorit kamu lagi promo! 
//  Pakai kode {{3}} untuk diskon 20%!"

Tracking & Analytics

Track Broadcast Campaign:

javascript

const campaign = {
    id: 'CAMP-20260217-001',
    name: 'Promo February',
    templateName: 'promo_feb',
    recipients: 1000,
    status: 'running',
    stats: {
        queued: 1000,
        sent: 0,
        delivered: 0,
        read: 0,
        failed: 0
    },
    startedAt: new Date()
};

// Update dari webhook
async function handleStatusUpdate(status) {
    const campaignId = await getCampaignFromMessageId(status.id);
    
    if (status.status === 'sent') {
        await db.campaigns.updateOne(
            { id: campaignId },
            { $inc: { 'stats.sent': 1, 'stats.queued': -1 } }
        );
    }
    
    if (status.status === 'delivered') {
        await db.campaigns.updateOne(
            { id: campaignId },
            { $inc: { 'stats.delivered': 1 } }
        );
    }
    
    if (status.status === 'read') {
        await db.campaigns.updateOne(
            { id: campaignId },
            { $inc: { 'stats.read': 1 } }
        );
    }
}

// Dashboard
function getCampaignStats(campaign) {
    return {
        deliveryRate: (campaign.stats.delivered / campaign.stats.sent * 100).toFixed(2) + '%',
        readRate: (campaign.stats.read / campaign.stats.delivered * 100).toFixed(2) + '%',
        failureRate: (campaign.stats.failed / campaign.recipients * 100).toFixed(2) + '%'
    };
}

Segmentation

javascript

// Segment recipients sebelum broadcast
async function segmentRecipients() {
    return {
        vip: await db.customers.find({ tier: 'vip', optIn: true }),
        active: await db.customers.find({ 
            lastOrder: { $gte: daysAgo(30) },
            optIn: true 
        }),
        dormant: await db.customers.find({ 
            lastOrder: { $lt: daysAgo(90) },
            optIn: true 
        }),
        newCustomers: await db.customers.find({
            createdAt: { $gte: daysAgo(7) },
            optIn: true
        })
    };
}

// Different campaigns per segment
async function runSegmentedCampaign() {
    const segments = await segmentRecipients();
    
    // VIP: send first, best timing
    await scheduleBroadcast({
        recipients: segments.vip,
        templateName: 'vip_exclusive',
        scheduledAt: '10:00'
    });
    
    // Active: regular promo
    await scheduleBroadcast({
        recipients: segments.active,
        templateName: 'promo_regular',
        scheduledAt: '12:00'
    });
    
    // Dormant: win-back offer
    await scheduleBroadcast({
        recipients: segments.dormant,
        templateName: 'winback_offer',
        scheduledAt: '14:00'
    });
}

Best Practices

DO ✅

- Gunakan template (official)
- Respect rate limits
- Queue untuk volume besar
- Personalisasi konten
- Track delivery & read
- Opt-in only!
- Segment recipients

DON'T ❌

- Spam tanpa consent
- Ignore rate limits
- Kirim semua sekaligus
- Generic message ke semua
- Skip tracking
- Blast ke non-opt-in
- Same message ke everyone

FAQ

Berapa banyak bisa kirim per hari?

Official: Tergantung tier (1K - unlimited). Unofficial: Max 200-300 safely.

Perlu opt-in?

WAJIB! Tanpa opt-in = spam = ban + legal issues.

Kenapa message failed?

Cek: Invalid number, blocked, rate limited, template rejected.


Kesimpulan

Broadcast smart, not hard!

Spam BroadcastSmart Broadcast
Blast semuaSegmented
No personalizationPersonalized
Ignore limitsRespect limits
No trackingFull analytics

Setup Broadcast System →


Artikel Terkait