WhatsApp API Contacts & Groups
Panduan lengkap WhatsApp API untuk contacts dan groups. Cek nomor valid, manage groups, broadcast. Tutorial developer!
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 settingsDON'T ❌
- Skip validasi nomor
- Hardcode format nomor
- Spam groups
- Add tanpa consent
- Ignore unsubscribe
- Store tanpa encryptFAQ
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!
| Manual | Automated |
|---|---|
| Cek satu-satu | Bulk validation |
| Manual manage | Auto segment |
| No tracking | Full history |