DEV Community

Cover image for Building WhatsApp Integrations for Indian Businesses: A Developer's Notes
botsense seo
botsense seo

Posted on

Building WhatsApp Integrations for Indian Businesses: A Developer's Notes

(https://botsense.io/whatsapp-api-provider-chennai/)
I've been building WhatsApp API integrations for small and medium businesses, mostly in India. Here are some notes that might help other developers working in this space.
The basics first
WhatsApp Business API works through Business Solution Providers (BSPs). You don't directly access Meta's API in most cases - you work with your BSP's API which wraps Meta's functionality.
Common BSPs in India: Gupshup, Twilio, MessageBird, Wati, BotSense, and others.
Each BSP has slightly different API structures, webhook formats, and dashboard features. Choose based on:

Pricing model (per-message vs. bundled)
API documentation quality
Webhook reliability
Template approval speed
Support responsiveness

Webhook handling
WhatsApp sends events to your webhook endpoint. The payload structure:
json{
"object": "whatsapp_business_account",
"entry": [{
"id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "PHONE_NUMBER",
"phone_number_id": "PHONE_NUMBER_ID"
},
"messages": [{
"from": "SENDER_PHONE_NUMBER",
"id": "MESSAGE_ID",
"timestamp": "TIMESTAMP",
"type": "text",
"text": {
"body": "MESSAGE_BODY"
}
}]
},
"field": "messages"
}]
}]
}
Some things I learned the hard way:
Always respond with 200 quickly. WhatsApp expects acknowledgment within seconds. Do async processing - acknowledge receipt, then process.
javascriptapp.post('/webhook', async (req, res) => {
// Acknowledge immediately
res.sendStatus(200);

// Process async
processMessage(req.body).catch(console.error);
});
Handle duplicate webhooks. WhatsApp sometimes sends the same event twice. Store message IDs and check before processing.
Webhook verification: WhatsApp sends a GET request to verify your endpoint. Handle it:
javascriptapp.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 === YOUR_VERIFY_TOKEN) {
res.status(200).send(challenge);
} else {
res.sendStatus(403);
}
});


## Template management

Templates are required for messages sent outside the 24-hour customer service window.

Template categories:
- **MARKETING** - Promotions, offers
- **UTILITY** - Order updates, reminders
- **AUTHENTICATION** - OTPs, login codes

Template components:
- Header (optional): TEXT, IMAGE, VIDEO, DOCUMENT
- Body: Main message with variables {{1}}, {{2}}, etc.
- Footer (optional): Small text
- Buttons (optional): Quick reply or call-to-action

**Indian market specifics:**

Regional language templates work well. I've seen better engagement with Tamil, Hindi, Telugu templates compared to English-only.

Template example for Chennai retail:
Enter fullscreen mode Exit fullscreen mode

Header: IMAGE
Body: வணக்கம் {{1}}! உங்கள் ஆர்டர் #{{2}} அனுப்பப்பட்டது. டெலிவரி: {{3}}
Footer: Thank you for shopping with us
Buttons: [Track Order] [Contact Support]
The 24-hour window
This trips up many implementations.

Customer messages you → 24-hour window opens
During window: Send any message type (free-form or template)
After window: Only template messages work

Track window status per conversation:
javascriptconst conversationWindows = new Map();

function updateWindow(phoneNumber) {
conversationWindows.set(phoneNumber, Date.now());
}

function isWindowOpen(phoneNumber) {
const lastMessage = conversationWindows.get(phoneNumber);
if (!lastMessage) return false;

const hoursSince = (Date.now() - lastMessage) / (1000 * 60 * 60);
return hoursSince < 24;
}

function canSendFreeform(phoneNumber) {
return isWindowOpen(phoneNumber);
}


## Common integration patterns

**E-commerce order updates:**
Enter fullscreen mode Exit fullscreen mode

Order placed → Send confirmation template
Payment received → Send receipt template

Order shipped → Send tracking template
Order delivered → Send feedback request template
Integrate with your order management system via webhooks:
javascript// Shopify webhook handler
app.post('/shopify/order-created', async (req, res) => {
const order = req.body;
const phone = formatIndianNumber(order.customer.phone);

await sendWhatsAppTemplate(phone, 'order_confirmation', {
customerName: order.customer.first_name,
orderId: order.order_number,
total: order.total_price
});

res.sendStatus(200);
});
Appointment booking:
javascript// Slot availability check
app.post('/check-slots', async (req, res) => {
const { date, service } = req.body;
const slots = await getAvailableSlots(date, service);

// Return as interactive buttons
const response = {
type: 'interactive',
interactive: {
type: 'button',
body: { text: Available slots for ${date}: },
action: {
buttons: slots.slice(0, 3).map((slot, i) => ({
type: 'reply',
reply: { id: slot_${i}, title: slot.time }
}))
}
}
};

await sendWhatsAppMessage(phone, response);
});
Indian number formatting
Indian numbers come in various formats. Normalize them:
javascriptfunction formatIndianNumber(phone) {
// Remove all non-digits
let cleaned = phone.replace(/\D/g, '');

// Handle different formats
if (cleaned.startsWith('91') && cleaned.length === 12) {
return cleaned; // Already correct: 919876543210
}
if (cleaned.startsWith('0') && cleaned.length === 11) {
return '91' + cleaned.slice(1); // 09876543210 → 919876543210
}
if (cleaned.length === 10) {
return '91' + cleaned; // 9876543210 → 919876543210
}

throw new Error(Invalid phone format: ${phone});
}
Error handling
WhatsApp API errors to handle:

131047 - Re-engagement message outside window
131051 - Template not approved
131026 - Message undeliverable (user blocked or number invalid)
130472 - Rate limited

javascriptasync function sendWithRetry(phone, message, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await sendWhatsAppMessage(phone, message);
} catch (error) {
if (error.code === 130472) {
// Rate limited - wait and retry
await sleep(Math.pow(2, i) * 1000);
continue;
}
if (error.code === 131026) {
// Undeliverable - don't retry
await markNumberInvalid(phone);
throw error;
}
throw error;
}
}
}




## Useful resources

- Meta's official docs: https://developers.facebook.com/docs/whatsapp
- BSP documentation (varies by provider)
- For Chennai/India business context: [BotSense](https://botsense.io/whatsapp-api-provider-chennai/) has decent documentation for local use cases

## Lessons learned

1. **Test with real Indian numbers.** WhatsApp behaves differently across regions.

2. **Monitor webhook delivery.** Set up alerts for webhook failures.

3. **Template approval takes time.** Submit templates early in the project.

4. **Regional languages matter.** English-only doesn't work for all audiences.

5. **The 24-hour window is strict.** Design your flows around it.

6. **Rate limits exist.** Don't blast thousands of messages without proper queuing.

Happy to answer questions in comments.
![ ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8j8bfmfpu1grhgb2pieh.png)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)