# WhatsApp Business API Integration Guide for Malaysian Developers
WhatsApp has over 24 million users in Malaysia. If you are building a SaaS product, CRM, or customer engagement tool for the Malaysian market, WhatsApp Business API integration is not optional — it is expected.
This guide covers everything you need to integrate the WhatsApp Cloud API into your product, with specific notes for Malaysian businesses.
Cloud API vs On-Premise API
Meta officially deprecated the on-premise API in October 2025. The Cloud API is now the only path forward for new integrations.
| Feature | Cloud API | On-Premise (Deprecated) |
|---|---|---|
| Hosting | Meta-managed | Self-hosted |
| Setup time | Minutes | Days to weeks |
| Scaling | Automatic | Manual infrastructure |
| Cost | Free API access | Server costs + licensing |
| Maintenance | Zero | Ongoing ops burden |
| Media storage | 30 days on Meta servers | Your own storage |
The Cloud API is hosted entirely by Meta. You send HTTP requests to graph.facebook.com, and Meta handles delivery, encryption, and infrastructure. There is no server to manage, no Docker container to run, and no downtime to worry about.
For Malaysian developers building products today, Cloud API is the only sensible choice.
Meta Business Verification for Malaysian Businesses
Before you can send messages to customers, your Meta Business Account must be verified. For Malaysian businesses, this means:
Required Documents
- SSM Certificate (Sijil Pendaftaran Perniagaan / Suruhanjaya Syarikat Malaysia) — this is the primary document Meta accepts for Malaysian business verification
- Business phone number (must not already be registered on WhatsApp)
- Business website with matching domain
Verification Process
- Go to Meta Business Settings and click "Start Verification"
- Enter your business details exactly as they appear on your SSM certificate
- Upload your SSM cert (PDF or clear photo)
- Meta will review within 1-5 business days (typically 2-3 days for Malaysian businesses)
- You may receive a verification code via phone or email
Common Rejection Reasons
- Business name does not match SSM certificate exactly (watch for Sdn Bhd vs SDN BHD)
- Website domain does not match the business name
- SSM certificate is expired — make sure your annual return is up to date
- Phone number is already associated with another WhatsApp account
Embedded Signup: The SaaS-Friendly Approach
If you are building a SaaS platform where your clients need their own WhatsApp Business Account (WABA), Embedded Signup is the correct approach.
How It Works
- Your client clicks "Connect WhatsApp" in your app
- A Meta popup guides them through creating or connecting a WABA
- On completion, you receive an access token and WABA ID
- Your client owns the WABA — you get API access as a Solution Partner
Why Embedded Signup Wins for SaaS
- Client ownership: The client owns their WABA and phone number — not you
- No manual onboarding: Eliminates back-and-forth CSV uploads and support tickets
- Instant setup: Client goes from zero to sending messages in under 5 minutes
- Compliance: Each client handles their own Meta Business Verification
Implementation
Add the Meta SDK to your frontend:
// Load Facebook SDK
window.fbAsyncInit = function () {
FB.init({
appId: 'YOUR_APP_ID',
autoLogAppEvents: true,
xfbml: true,
version: 'v21.0'
});
};
// Launch Embedded Signup
function launchWhatsAppSignup() {
FB.login(
function (response) {
if (response.authResponse) {
const accessToken = response.authResponse.accessToken;
// Send this token to your backend
// Exchange for a permanent System User token
console.log('Access token:', accessToken);
}
},
{
config_id: 'YOUR_CONFIG_ID',
response_type: 'code',
override_default_response_type: true,
extras: {
setup: {
// Pre-fill business info if available
}
}
}
);
}
Webhook Setup: Receiving Messages
Your webhook endpoint handles two things: verification challenges from Meta, and incoming message payloads.
Express.js Webhook Handler
import express from 'express';
const app = express();
app.use(express.json());
const VERIFY_TOKEN = process.env.WHATSAPP_VERIFY_TOKEN;
// Verification challenge (GET)
app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
if (mode === 'subscribe' && token === VERIFY_TOKEN) {
console.log('Webhook verified');
return res.status(200).send(challenge);
}
return res.sendStatus(403);
});
// Incoming messages (POST)
app.post('/webhook', (req, res) => {
const body = req.body;
if (body.object !== 'whatsapp_business_account') {
return res.sendStatus(404);
}
// Always respond 200 quickly — process async
res.sendStatus(200);
for (const entry of body.entry) {
for (const change of entry.changes) {
if (change.field !== 'messages') continue;
const messages = change.value.messages || [];
const contacts = change.value.contacts || [];
for (const message of messages) {
handleIncomingMessage(message, contacts, change.value.metadata);
}
}
}
});
function handleIncomingMessage(message: any, contacts: any[], metadata: any) {
const from = message.from; // sender phone number
const phoneNumberId = metadata.phone_number_id;
switch (message.type) {
case 'text':
console.log(\`Text from \${from}: \${message.text.body}\`);
break;
case 'image':
case 'document':
case 'video':
console.log(\`Media from \${from}: \${message[message.type].id}\`);
// Download media via GET /v21.0/{media-id}
break;
case 'interactive':
// Button replies, list replies
console.log(\`Interactive from \${from}\`);
break;
default:
console.log(\`Unhandled type: \${message.type}\`);
}
}
Sending Replies
async function sendTextMessage(
phoneNumberId: string,
to: string,
text: string,
accessToken: string
) {
const response = await fetch(
\`https://graph.facebook.com/v21.0/\${phoneNumberId}/messages\`,
{
method: 'POST',
headers: {
'Authorization': \`Bearer \${accessToken}\`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
messaging_product: 'whatsapp',
recipient_type: 'individual',
to,
type: 'text',
text: { preview_url: false, body: text }
})
}
);
return response.json();
}
Handling Media Downloads
async function downloadMedia(mediaId: string, accessToken: string) {
// Step 1: Get media URL
const metaRes = await fetch(
\`https://graph.facebook.com/v21.0/\${mediaId}\`,
{ headers: { Authorization: \`Bearer \${accessToken}\` } }
);
const { url } = await metaRes.json();
// Step 2: Download the actual file
const fileRes = await fetch(url, {
headers: { Authorization: \`Bearer \${accessToken}\` }
});
return fileRes.buffer();
}
Conversation-Based Pricing
Meta charges per 24-hour conversation window, not per message. Understanding the pricing model is critical before you build.
| Category | Who Opens | Cost (Malaysia) | Example |
|---|---|---|---|
| Marketing | You (business) | ~RM 0.36 / conversation | Promotions, announcements |
| Utility | You (business) | ~RM 0.20 / conversation | Order updates, receipts |
| Authentication | You (business) | ~RM 0.13 / conversation | OTP codes |
| Service | Customer initiates | Free (first 1,000/month) | Customer replies to you |
Free Tier
Every WABA gets 1,000 free service conversations per month. This means if your customers message you first, the first 1,000 conversations cost nothing. For most Malaysian SMEs, this covers a significant portion of their volume.
Key Pricing Rules
- A conversation window lasts 24 hours from the first message in that category
- Multiple messages within the same window do not incur additional charges
- If a customer messages you and you reply within 24 hours, that is a service conversation (free tier eligible)
- If you initiate contact, you must use an approved template message, and it counts as marketing/utility/authentication
Template Messages
You cannot send the first message to a customer using free-form text. You must use a pre-approved template.
Creating Templates
Templates are created in the WhatsApp Manager or via API:
async function createTemplate(wabaId: string, accessToken: string) {
const response = await fetch(
\`https://graph.facebook.com/v21.0/\${wabaId}/message_templates\`,
{
method: 'POST',
headers: {
Authorization: \`Bearer \${accessToken}\`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'order_confirmation',
language: 'en',
category: 'UTILITY',
components: [
{
type: 'BODY',
text: 'Hi {{1}}, your order #{{2}} has been confirmed. Estimated delivery: {{3}}.',
example: {
body_text: [['Ahmad', '12345', '2 March 2026']]
}
}
]
})
}
);
return response.json();
}
Approval Tips
- Templates are reviewed by Meta within 24-48 hours
- Use clear, specific language — vague templates get rejected
- Do not include URLs in the body text unless using a URL button component
- Avoid words like "free", "win", "prize" — these trigger spam filters
- Include example values for all variables
- Malay language templates are supported — set
language: 'ms'
Common Pitfalls
The 24-Hour Window
Once a customer messages you, you have 24 hours to reply with free-form text. After that window closes, you must use a template message (which costs money). Build your system to track conversation windows and warn agents before they expire.
Rate Limits
- New WABAs start at 250 business-initiated conversations per day
- After phone number verification and quality rating, this increases to 1,000 → 10,000 → 100,000
- API rate limit: 80 messages per second per phone number
- Webhook delivery: Meta retries failed webhooks for up to 7 days
Phone Number Rules
- One phone number per WABA — you cannot register the same number on two accounts
- If the number is currently on WhatsApp (personal or Business app), you must delete that account first
- Porting from Business app to API is supported but requires a 2-step migration
- Malaysian numbers (+60) work without issues — both mobile and landline with SMS verification
Quality Rating
Meta monitors your message quality. If too many users block or report you, your quality rating drops from Green → Yellow → Red. A red rating restricts your messaging limits. Monitor your quality rating in WhatsApp Manager regularly.
Production Example: ForwardChat
Building a production WhatsApp integration from scratch takes 3-6 months when you account for edge cases: webhook reliability, media handling, multi-device support, template management, and conversation routing.
ForwardChat is an example of a production-grade WhatsApp platform built for the Malaysian market. It handles Embedded Signup onboarding, multi-agent chat routing, and automated responses — all running on the Cloud API infrastructure described in this guide.
If you are building a SaaS product, consider whether building the WhatsApp layer yourself is the best use of your engineering time, or whether leveraging an existing platform makes more sense.
Integration with AI
WhatsApp API pairs well with AI services for automated customer support. You can pipe incoming messages through an LLM to generate contextual replies, classify intent, or escalate to human agents. If you are exploring this path, our AI development services cover ChatGPT and custom LLM integration for production workloads.
The combination of WhatsApp's reach in Malaysia with AI-powered automation is where the highest ROI lies for most businesses.
Summary
The WhatsApp Cloud API is mature, well-documented, and free to use (you only pay for conversations). For Malaysian developers:
- Start with Cloud API — on-premise is deprecated
- Get your SSM cert ready for Meta Business Verification
- Use Embedded Signup if building multi-tenant SaaS
- Respect the 24-hour window and plan your template strategy
- Monitor quality rating to maintain messaging limits
- Start with the free tier — 1,000 service conversations per month covers most SMEs
Need WhatsApp API Integration for Your Product?
Building WhatsApp into your product is straightforward for basic use cases, but production-grade integration with multi-tenant support, AI automation, and reliable webhook processing requires significant engineering investment.
Forward Genix has built multiple WhatsApp-integrated platforms for Malaysian and international clients. Get in touch to discuss your integration requirements.

