WhatsApp API Contacts & Groups

Panduan lengkap WhatsApp API untuk contacts dan groups. Cek nomor valid, manage groups, broadcast. Tutorial developer!

WhatsApp API Contacts & Groups
WhatsApp API Contacts & Groups

Contacts & Groups = Foundation!

Mengelola contacts dan groups adalah dasar dari WhatsApp automation. Panduan ini menjelaskan cara validasi nomor, manage contacts, dan handle groups.


Validasi Nomor WhatsApp

Cek Nomor Valid (Official API):

javascript

const axios = require('axios');

async function checkPhoneNumber(phoneNumber) {
    const url = `https://graph.facebook.com/v17.0/${PHONE_NUMBER_ID}/contacts`;
    
    const payload = {
        blocking: 'wait',
        contacts: [phoneNumber],
        force_check: true
    };
    
    try {
        const response = await axios.post(url, payload, {
            headers: {
                'Authorization': `Bearer ${ACCESS_TOKEN}`,
                'Content-Type': 'application/json'
            }
        });
        
        const contact = response.data.contacts[0];
        
        return {
            input: contact.input,
            wa_id: contact.wa_id,
            status: contact.status // 'valid' atau 'invalid'
        };
    } catch (error) {
        return { status: 'error', error: error.message };
    }
}

// Bulk check
async function checkMultipleNumbers(phoneNumbers) {
    const url = `https://graph.facebook.com/v17.0/${PHONE_NUMBER_ID}/contacts`;
    
    const payload = {
        blocking: 'wait',
        contacts: phoneNumbers,
        force_check: true
    };
    
    const response = await axios.post(url, payload, {
        headers: {
            'Authorization': `Bearer ${ACCESS_TOKEN}`,
            'Content-Type': 'application/json'
        }
    });
    
    return response.data.contacts.map(c => ({
        input: c.input,
        wa_id: c.wa_id,
        isValid: c.status === 'valid'
    }));
}

// Usage
const results = await checkMultipleNumbers([
    '628123456789',
    '628987654321',
    '621234567890'
]);

console.log(results);
// [
//   { input: '628123456789', wa_id: '628123456789', isValid: true },
//   { input: '628987654321', wa_id: '628987654321', isValid: true },
//   { input: '621234567890', wa_id: null, isValid: false }
// ]

Cek Nomor (Unofficial - Baileys):

javascript

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

async function isOnWhatsApp(sock, phoneNumber) {
    const jid = `${phoneNumber}@s.whatsapp.net`;
    
    try {
        const [result] = await sock.onWhatsApp(jid);
        
        return {
            exists: result?.exists || false,
            jid: result?.jid
        };
    } catch (error) {
        return { exists: false, error: error.message };
    }
}

// Bulk check
async function checkMultipleOnWhatsApp(sock, phoneNumbers) {
    const jids = phoneNumbers.map(p => `${p}@s.whatsapp.net`);
    const results = await sock.onWhatsApp(...jids);
    
    return results.map(r => ({
        jid: r.jid,
        exists: r.exists
    }));
}

// Usage
const numbers = ['628123456789', '628987654321'];
const results = await checkMultipleOnWhatsApp(sock, numbers);

Format Nomor

Normalisasi:

javascript

function normalizePhoneNumber(phone, defaultCountry = '62') {
    // Hapus karakter non-digit
    let cleaned = phone.replace(/\D/g, '');
    
    // Handle format Indonesia
    if (cleaned.startsWith('0')) {
        cleaned = defaultCountry + cleaned.substring(1);
    }
    
    // Handle format +62
    if (cleaned.startsWith('+')) {
        cleaned = cleaned.substring(1);
    }
    
    // Validasi panjang (Indonesia: 10-13 digit setelah kode negara)
    if (cleaned.startsWith('62')) {
        const localNumber = cleaned.substring(2);
        if (localNumber.length < 9 || localNumber.length > 12) {
            return { valid: false, error: 'Invalid length' };
        }
    }
    
    return { valid: true, normalized: cleaned };
}

// Test
console.log(normalizePhoneNumber('08123456789'));  // { valid: true, normalized: '628123456789' }
console.log(normalizePhoneNumber('+62 812-3456-789')); // { valid: true, normalized: '628123456789' }
console.log(normalizePhoneNumber('62812345678'));  // { valid: true, normalized: '62812345678' }

Manage Contacts

Simpan Contact Info:

javascript

// Dari webhook - extract contact info
function extractContactInfo(webhookData) {
    const contact = webhookData.contacts?.[0];
    
    if (contact) {
        return {
            wa_id: contact.wa_id,
            name: contact.profile?.name || 'Unknown',
            phone: contact.wa_id
        };
    }
    
    return null;
}

// Simpan ke database
async function saveContact(contactInfo, additionalData = {}) {
    const contact = {
        wa_id: contactInfo.wa_id,
        phone: contactInfo.phone,
        name: contactInfo.name,
        ...additionalData,
        firstContact: new Date(),
        lastContact: new Date()
    };
    
    await db.contacts.updateOne(
        { wa_id: contact.wa_id },
        { $set: contact, $setOnInsert: { firstContact: new Date() } },
        { upsert: true }
    );
    
    return contact;
}

Get Profile Picture (Unofficial):

javascript

async function getProfilePicture(sock, phoneNumber) {
    const jid = `${phoneNumber}@s.whatsapp.net`;
    
    try {
        const ppUrl = await sock.profilePictureUrl(jid, 'image');
        return ppUrl;
    } catch (error) {
        // No profile picture or privacy settings
        return null;
    }
}

// Get status/about
async function getStatus(sock, phoneNumber) {
    const jid = `${phoneNumber}@s.whatsapp.net`;
    
    try {
        const status = await sock.fetchStatus(jid);
        return status?.status || null;
    } catch (error) {
        return null;
    }
}

Group Management

Create Group (Unofficial):

javascript

async function createGroup(sock, groupName, participants) {
    // participants = ['628123456789', '628987654321']
    const participantJids = participants.map(p => `${p}@s.whatsapp.net`);
    
    const group = await sock.groupCreate(groupName, participantJids);
    
    return {
        id: group.id,
        name: groupName,
        participants: participantJids
    };
}

// Usage
const group = await createGroup(sock, 'Team Project', [
    '628123456789',
    '628987654321'
]);

Get Group Info:

javascript

async function getGroupInfo(sock, groupId) {
    // groupId format: '[email protected]'
    const metadata = await sock.groupMetadata(groupId);
    
    return {
        id: metadata.id,
        name: metadata.subject,
        description: metadata.desc,
        owner: metadata.owner,
        participants: metadata.participants.map(p => ({
            id: p.id,
            admin: p.admin, // 'admin', 'superadmin', atau null
            isAdmin: p.admin !== null
        })),
        createdAt: new Date(metadata.creation * 1000)
    };
}

Group Operations:

javascript

// Tambah participant
async function addToGroup(sock, groupId, participants) {
    const jids = participants.map(p => `${p}@s.whatsapp.net`);
    await sock.groupParticipantsUpdate(groupId, jids, 'add');
}

// Remove participant
async function removeFromGroup(sock, groupId, participants) {
    const jids = participants.map(p => `${p}@s.whatsapp.net`);
    await sock.groupParticipantsUpdate(groupId, jids, 'remove');
}

// Promote to admin
async function promoteToAdmin(sock, groupId, participants) {
    const jids = participants.map(p => `${p}@s.whatsapp.net`);
    await sock.groupParticipantsUpdate(groupId, jids, 'promote');
}

// Demote from admin
async function demoteFromAdmin(sock, groupId, participants) {
    const jids = participants.map(p => `${p}@s.whatsapp.net`);
    await sock.groupParticipantsUpdate(groupId, jids, 'demote');
}

// Update group subject/name
async function updateGroupName(sock, groupId, newName) {
    await sock.groupUpdateSubject(groupId, newName);
}

// Update group description
async function updateGroupDescription(sock, groupId, description) {
    await sock.groupUpdateDescription(groupId, description);
}

// Leave group
async function leaveGroup(sock, groupId) {
    await sock.groupLeave(groupId);
}

Group Settings:

javascript

// Only admin can send messages
async function setGroupAdminOnly(sock, groupId, adminOnly = true) {
    await sock.groupSettingUpdate(
        groupId, 
        adminOnly ? 'announcement' : 'not_announcement'
    );
}

// Only admin can edit group info
async function setGroupInfoAdminOnly(sock, groupId, adminOnly = true) {
    await sock.groupSettingUpdate(
        groupId,
        adminOnly ? 'locked' : 'unlocked'
    );
}

Send to Group

Send Message to Group:

javascript

// Unofficial (Baileys)
async function sendToGroup(sock, groupId, message) {
    await sock.sendMessage(groupId, { text: message });
}

// Dengan mention
async function sendToGroupWithMention(sock, groupId, message, mentions) {
    const mentionJids = mentions.map(m => `${m}@s.whatsapp.net`);
    
    await sock.sendMessage(groupId, {
        text: message,
        mentions: mentionJids
    });
}

// Usage
await sendToGroupWithMention(
    sock,
    '[email protected]',
    'Hey @628123456789 dan @628987654321, meeting jam 3!',
    ['628123456789', '628987654321']
);

Broadcast to Multiple Groups:

javascript

async function broadcastToGroups(sock, groupIds, message) {
    const results = [];
    
    for (const groupId of groupIds) {
        try {
            await sock.sendMessage(groupId, { text: message });
            results.push({ groupId, success: true });
            
            // Delay antar group
            await sleep(2000);
        } catch (error) {
            results.push({ groupId, success: false, error: error.message });
        }
    }
    
    return results;
}

Handle Group Events

javascript

sock.ev.on('group-participants.update', async (update) => {
    const { id, participants, action } = update;
    // id = group ID
    // participants = array of JIDs
    // action = 'add', 'remove', 'promote', 'demote'
    
    console.log(`Group ${id}: ${action} - ${participants.join(', ')}`);
    
    if (action === 'add') {
        // Welcome new member
        for (const participant of participants) {
            await sock.sendMessage(id, {
                text: `Selamat datang @${participant.split('@')[0]}! 👋`,
                mentions: [participant]
            });
        }
    }
    
    if (action === 'remove') {
        // Log member left/removed
        console.log(`Member removed: ${participants}`);
    }
});

// Group update (name, description, etc)
sock.ev.on('groups.update', async (updates) => {
    for (const update of updates) {
        console.log(`Group ${update.id} updated:`, update);
    }
});

Contact Segmentation

javascript

// Segment contacts untuk targeting
async function segmentContacts() {
    const allContacts = await db.contacts.find({}).toArray();
    
    return {
        active: allContacts.filter(c => 
            c.lastContact > daysAgo(30)
        ),
        dormant: allContacts.filter(c => 
            c.lastContact < daysAgo(30) && c.lastContact > daysAgo(90)
        ),
        inactive: allContacts.filter(c => 
            c.lastContact < daysAgo(90)
        ),
        vip: allContacts.filter(c => 
            c.totalOrders > 5 || c.totalSpent > 1000000
        ),
        newContacts: allContacts.filter(c =>
            c.firstContact > daysAgo(7)
        )
    };
}

// Tag contacts
async function tagContact(wa_id, tags) {
    await db.contacts.updateOne(
        { wa_id },
        { $addToSet: { tags: { $each: tags } } }
    );
}

// Get contacts by tag
async function getContactsByTag(tag) {
    return await db.contacts.find({ tags: tag }).toArray();
}

Best Practices

DO ✅

- Validasi nomor sebelum kirim
- Normalize format nomor
- Track contact activity
- Segment untuk targeting
- Handle group events
- Respect privacy settings

DON'T ❌

- Skip validasi nomor
- Hardcode format nomor
- Spam groups
- Add tanpa consent
- Ignore unsubscribe
- Store tanpa encrypt

FAQ

Bagaimana tahu nomor valid?

Official API: gunakan /contacts endpoint. Unofficial: onWhatsApp() method.

Bisa kirim ke group via Official API?

Tidak langsung. Official API fokus business-to-customer, bukan groups.

Rate limit untuk check nomor?

Official: termasuk dalam rate limit umum. Unofficial: hati-hati, jangan terlalu sering.


Kesimpulan

Contacts & Groups = Foundation automation!

ManualAutomated
Cek satu-satuBulk validation
Manual manageAuto segment
No trackingFull history

Manage Contacts Sekarang →


Artikel Terkait