Cara Balas WA Otomatis dengan Analytics
Tutorial track performa auto reply WhatsApp. Response time, conversion, engagement metrics. Data-driven optimization!
Analytics = Know what works!
Tanpa data, kamu tidak tahu apakah auto reply bekerja dengan baik. Analytics membantu measure, analyze, dan improve.
Key Metrics
📊 METRICS PENTING:
VOLUME:
- Total messages (in/out)
- Unique conversations
- New vs returning customers
RESPONSE:
- First response time
- Average response time
- Response rate
ENGAGEMENT:
- Reply rate
- Conversation length
- Drop-off points
CONVERSION:
- Chat to lead rate
- Lead to customer rate
- Revenue from chat
SATISFACTION:
- CSAT score
- Resolution rate
- Escalation rateEvent Tracking
Track Everything:
javascript
// Analytics event types
const EventTypes = {
MESSAGE_RECEIVED: 'message_received',
MESSAGE_SENT: 'message_sent',
CONVERSATION_STARTED: 'conversation_started',
CONVERSATION_RESOLVED: 'conversation_resolved',
BOT_RESPONSE: 'bot_response',
HUMAN_TAKEOVER: 'human_takeover',
KEYWORD_TRIGGERED: 'keyword_triggered',
MENU_SELECTED: 'menu_selected',
LINK_CLICKED: 'link_clicked',
CONVERSION: 'conversion'
};
// Track event
async function trackEvent(eventType, data) {
const event = {
id: generateId(),
type: eventType,
timestamp: new Date(),
...data
};
await db.analyticsEvents.insertOne(event);
// Real-time dashboard update
io.to('analytics').emit('new-event', event);
}
// Usage examples
await trackEvent(EventTypes.MESSAGE_RECEIVED, {
conversationId: conv.id,
customerId: from,
messageType: 'text',
messageLength: message.length
});
await trackEvent(EventTypes.BOT_RESPONSE, {
conversationId: conv.id,
triggeredBy: 'keyword',
keyword: 'harga',
responseTime: 150 // ms
});
await trackEvent(EventTypes.CONVERSION, {
conversationId: conv.id,
customerId: from,
type: 'order',
value: 500000
});Response Time Tracking
Calculate Response Time:
javascript
async function trackResponseTime(conversationId, direction) {
if (direction === 'incoming') {
// Customer mengirim pesan, catat waktu
await db.responseTracking.updateOne(
{ conversationId },
{
$set: {
lastIncomingAt: new Date(),
awaitingResponse: true
}
},
{ upsert: true }
);
}
if (direction === 'outgoing') {
// Bot/admin membalas
const tracking = await db.responseTracking.findOne({ conversationId });
if (tracking?.awaitingResponse) {
const responseTime = Date.now() - tracking.lastIncomingAt.getTime();
await trackEvent('response_time', {
conversationId,
responseTimeMs: responseTime,
responder: 'bot' // atau 'human'
});
await db.responseTracking.updateOne(
{ conversationId },
{ $set: { awaitingResponse: false } }
);
}
}
}
// Calculate averages
async function getAverageResponseTime(startDate, endDate) {
const result = await db.analyticsEvents.aggregate([
{
$match: {
type: 'response_time',
timestamp: { $gte: startDate, $lte: endDate }
}
},
{
$group: {
_id: '$responder',
avgResponseTime: { $avg: '$responseTimeMs' },
minResponseTime: { $min: '$responseTimeMs' },
maxResponseTime: { $max: '$responseTimeMs' },
count: { $sum: 1 }
}
}
]).toArray();
return result;
}Conversation Analytics
Conversation Flow Analysis:
javascript
async function analyzeConversationFlows(startDate, endDate) {
// Get all conversations
const conversations = await db.conversations.find({
createdAt: { $gte: startDate, $lte: endDate }
}).toArray();
const analysis = {
totalConversations: conversations.length,
avgMessagesPerConversation: 0,
avgDuration: 0,
resolutionRate: 0,
botOnlyResolved: 0,
humanInvolved: 0,
dropOffPoints: {}
};
let totalMessages = 0;
let totalDuration = 0;
let resolved = 0;
for (const conv of conversations) {
const messages = await db.messages.find({
conversationId: conv.id
}).toArray();
totalMessages += messages.length;
if (conv.status === 'resolved') {
resolved++;
totalDuration += conv.resolvedAt - conv.createdAt;
// Check if bot-only or human involved
const humanMessages = messages.filter(m =>
m.direction === 'outgoing' && m.senderId !== 'bot'
);
if (humanMessages.length === 0) {
analysis.botOnlyResolved++;
} else {
analysis.humanInvolved++;
}
}
// Analyze drop-off
const lastMessage = messages[messages.length - 1];
if (conv.status !== 'resolved' && lastMessage.direction === 'outgoing') {
// Customer didn't reply - analyze last bot message
const dropPoint = extractDropPoint(lastMessage.content);
analysis.dropOffPoints[dropPoint] = (analysis.dropOffPoints[dropPoint] || 0) + 1;
}
}
analysis.avgMessagesPerConversation = totalMessages / conversations.length;
analysis.avgDuration = totalDuration / resolved; // in ms
analysis.resolutionRate = (resolved / conversations.length * 100).toFixed(1);
return analysis;
}Keyword Performance
javascript
async function getKeywordPerformance(startDate, endDate) {
const result = await db.analyticsEvents.aggregate([
{
$match: {
type: 'keyword_triggered',
timestamp: { $gte: startDate, $lte: endDate }
}
},
{
$group: {
_id: '$keyword',
triggerCount: { $sum: 1 },
uniqueUsers: { $addToSet: '$customerId' }
}
},
{
$project: {
keyword: '$_id',
triggerCount: 1,
uniqueUsers: { $size: '$uniqueUsers' }
}
},
{
$sort: { triggerCount: -1 }
},
{
$limit: 20
}
]).toArray();
return result;
}
// Track keyword that didn't match
async function trackUnmatchedKeywords(message) {
await db.unmatchedKeywords.insertOne({
message: message.substring(0, 100),
timestamp: new Date()
});
}
// Analyze unmatched to find new keywords to add
async function getTopUnmatchedKeywords(limit = 20) {
// Simple word frequency analysis
const unmatched = await db.unmatchedKeywords.find({
timestamp: { $gte: daysAgo(30) }
}).toArray();
const wordFreq = {};
for (const item of unmatched) {
const words = item.message.toLowerCase().split(/\s+/);
for (const word of words) {
if (word.length > 3) { // Ignore short words
wordFreq[word] = (wordFreq[word] || 0) + 1;
}
}
}
return Object.entries(wordFreq)
.sort((a, b) => b[1] - a[1])
.slice(0, limit)
.map(([word, count]) => ({ word, count }));
}Conversion Tracking
javascript
// Track funnel
const funnelStages = [
'conversation_started',
'product_viewed',
'added_to_cart',
'checkout_started',
'payment_initiated',
'order_completed'
];
async function trackFunnelEvent(conversationId, stage, metadata = {}) {
await db.funnelEvents.insertOne({
conversationId,
stage,
stageIndex: funnelStages.indexOf(stage),
timestamp: new Date(),
...metadata
});
}
async function getFunnelAnalytics(startDate, endDate) {
const funnelData = {};
for (const stage of funnelStages) {
const count = await db.funnelEvents.countDocuments({
stage,
timestamp: { $gte: startDate, $lte: endDate }
});
funnelData[stage] = count;
}
// Calculate conversion rates between stages
const conversionRates = {};
for (let i = 1; i < funnelStages.length; i++) {
const prevStage = funnelStages[i - 1];
const currStage = funnelStages[i];
if (funnelData[prevStage] > 0) {
conversionRates[`${prevStage}_to_${currStage}`] =
(funnelData[currStage] / funnelData[prevStage] * 100).toFixed(1);
}
}
return { funnelData, conversionRates };
}Dashboard Reports
Daily Summary:
javascript
async function generateDailyReport(date) {
const startOfDay = new Date(date);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(date);
endOfDay.setHours(23, 59, 59, 999);
const report = {
date: date.toISOString().split('T')[0],
messages: {
received: await countEvents('message_received', startOfDay, endOfDay),
sent: await countEvents('message_sent', startOfDay, endOfDay)
},
conversations: {
started: await countEvents('conversation_started', startOfDay, endOfDay),
resolved: await countEvents('conversation_resolved', startOfDay, endOfDay)
},
responseTime: await getAverageResponseTime(startOfDay, endOfDay),
topKeywords: await getKeywordPerformance(startOfDay, endOfDay),
conversions: await getConversions(startOfDay, endOfDay),
peakHours: await getPeakHours(startOfDay, endOfDay)
};
return report;
}
// Get peak activity hours
async function getPeakHours(startDate, endDate) {
const result = await db.analyticsEvents.aggregate([
{
$match: {
type: 'message_received',
timestamp: { $gte: startDate, $lte: endDate }
}
},
{
$group: {
_id: { $hour: '$timestamp' },
count: { $sum: 1 }
}
},
{
$sort: { count: -1 }
}
]).toArray();
return result.map(r => ({
hour: r._id,
messageCount: r.count
}));
}Weekly Report:
javascript
async function generateWeeklyReport(weekStartDate) {
const days = [];
for (let i = 0; i < 7; i++) {
const date = new Date(weekStartDate);
date.setDate(date.getDate() + i);
days.push(await generateDailyReport(date));
}
// Calculate week totals and trends
const weekTotal = {
messages: days.reduce((sum, d) => sum + d.messages.received + d.messages.sent, 0),
conversations: days.reduce((sum, d) => sum + d.conversations.started, 0),
conversions: days.reduce((sum, d) => sum + d.conversions.count, 0),
revenue: days.reduce((sum, d) => sum + d.conversions.value, 0)
};
// Compare to previous week
const prevWeekStart = new Date(weekStartDate);
prevWeekStart.setDate(prevWeekStart.getDate() - 7);
const prevWeek = await generateWeeklySummary(prevWeekStart);
const trends = {
messages: calculateGrowth(weekTotal.messages, prevWeek.messages),
conversations: calculateGrowth(weekTotal.conversations, prevWeek.conversations),
conversions: calculateGrowth(weekTotal.conversions, prevWeek.conversions),
revenue: calculateGrowth(weekTotal.revenue, prevWeek.revenue)
};
return {
weekStart: weekStartDate,
days,
totals: weekTotal,
trends,
insights: generateInsights(days, trends)
};
}Visualization
Chart Data Formats:
javascript
// Line chart: Messages over time
async function getMessagesChartData(days = 30) {
const data = [];
for (let i = days - 1; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dayStart = new Date(date);
dayStart.setHours(0, 0, 0, 0);
const dayEnd = new Date(date);
dayEnd.setHours(23, 59, 59, 999);
const received = await countEvents('message_received', dayStart, dayEnd);
const sent = await countEvents('message_sent', dayStart, dayEnd);
data.push({
date: date.toISOString().split('T')[0],
received,
sent
});
}
return data;
}
// Pie chart: Message sources
async function getSourceDistribution(startDate, endDate) {
return await db.analyticsEvents.aggregate([
{
$match: {
type: 'conversation_started',
timestamp: { $gte: startDate, $lte: endDate }
}
},
{
$group: {
_id: '$source',
count: { $sum: 1 }
}
}
]).toArray();
}Automated Insights
javascript
function generateInsights(data, trends) {
const insights = [];
// Response time insight
if (data.avgResponseTime > 5 * 60 * 1000) { // > 5 menit
insights.push({
type: 'warning',
message: 'Response time rata-rata > 5 menit. Pertimbangkan tambah auto-reply.'
});
}
// Traffic trend
if (trends.messages > 20) {
insights.push({
type: 'positive',
message: `Traffic naik ${trends.messages}% dari minggu lalu!`
});
}
// Conversion insight
if (trends.conversions < -10) {
insights.push({
type: 'warning',
message: `Conversion turun ${Math.abs(trends.conversions)}%. Review bot flow.`
});
}
// Peak hour insight
const peakHour = data.peakHours[0];
insights.push({
type: 'info',
message: `Peak traffic jam ${peakHour.hour}:00. Pastikan coverage maksimal.`
});
return insights;
}Best Practices
DO ✅
- Track everything
- Set baseline metrics
- Review daily/weekly
- Act on insights
- A/B test changes
- Share with teamDON'T ❌
- Track tanpa action
- Ignore trends
- Review jarang
- Data hoarding
- Skip testing
- Keep data siloedFAQ
Metric mana yang paling penting?
Response time dan conversion rate. Langsung impact customer experience dan revenue.
Perlu tools khusus?
Bisa custom atau pakai tools seperti Metabase, Grafana untuk visualization.
Kesimpulan
Analytics = Continuous improvement!
| No Analytics | With Analytics |
|---|---|
| Guessing | Data-driven |
| No improvement | Continuous improve |
| Blind spots | Full visibility |
| Reactive | Proactive |