Build a WhatsApp Scam Checker Bot
Let users text a phone number to WhatsApp and get an instant risk assessment from the ScamVerify™ API.
This tutorial walks you through building a WhatsApp bot that lets anyone send a phone number and get back an instant scam risk assessment. The bot uses the Meta Cloud API for WhatsApp Business and the ScamVerify™ API for threat intelligence. By the end, you will have a working bot that anyone can message to check suspicious phone numbers.
Prerequisites
- Node.js 18 or later
- A ScamVerify™ API key (get one at scamverify.ai/settings/api)
- A Meta Business account with WhatsApp Business API access
- ngrok for local development
What You Will Build
A WhatsApp bot that:
- Receives incoming messages via the Meta Cloud API webhook
- Extracts phone numbers from the message text
- Calls the ScamVerify™ phone lookup API
- Sends a formatted reply with the risk score, verdict, and explanation
- Handles errors and invalid input gracefully
Set up the Meta WhatsApp Business API
-
Go to developers.facebook.com and create a new app (type: Business)
-
Add the WhatsApp product to your app
-
In the WhatsApp section, note your:
- Phone Number ID (the ID of the test number Meta provides)
- WhatsApp Business Account ID
- Temporary Access Token (for development, you will create a permanent one later)
-
Under Quickstart > Configuration, set up a webhook:
- Callback URL: Your server URL (set up in the next step)
- Verify Token: A secret string you choose (e.g.,
scamverify_webhook_secret) - Subscribe to:
messages
Meta provides a free test phone number for development. You can send and receive messages without going through the full business verification process.
Set up the project
mkdir whatsapp-scambot && cd whatsapp-scambot
npm init -y
npm install express dotenvCreate a .env file:
SCAMVERIFY_API_KEY=sv_live_your_key_here
WHATSAPP_TOKEN=your-meta-access-token
WHATSAPP_PHONE_ID=your-phone-number-id
WEBHOOK_VERIFY_TOKEN=scamverify_webhook_secret
PORT=3000Create the ScamVerify™ client
// scamverify.js
class ScamVerifyClient {
constructor(apiKey) {
if (!apiKey || !apiKey.startsWith('sv_')) {
throw new Error('Invalid API key. Keys must start with sv_live_ or sv_test_');
}
this.apiKey = apiKey;
this.baseUrl = 'https://scamverify.ai/api/v1';
}
async lookupPhone(phoneNumber) {
const response = await fetch(`${this.baseUrl}/phone/lookup`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone_number: phoneNumber }),
});
if (response.status === 429) {
throw new Error('Rate limited. Please try again in a minute.');
}
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
}
module.exports = { ScamVerifyClient };Create the WhatsApp message sender
Build a helper module for sending replies through the Meta Cloud API.
// whatsapp.js
const WHATSAPP_TOKEN = process.env.WHATSAPP_TOKEN;
const WHATSAPP_PHONE_ID = process.env.WHATSAPP_PHONE_ID;
async function sendMessage(to, text) {
const response = await fetch(
`https://graph.facebook.com/v18.0/${WHATSAPP_PHONE_ID}/messages`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${WHATSAPP_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messaging_product: 'whatsapp',
to,
type: 'text',
text: { body: text },
}),
}
);
if (!response.ok) {
const error = await response.json();
console.error('WhatsApp send error:', error);
throw new Error(`Failed to send message: ${response.status}`);
}
return response.json();
}
module.exports = { sendMessage };Create the message formatter
Format ScamVerify™ results into clean WhatsApp messages with emojis for visual clarity.
// formatter.js
const VERDICT_EMOJI = {
safe: '\u2705',
low_risk: '\ud83d\udfe2',
medium_risk: '\u26a0\ufe0f',
high_risk: '\ud83d\udea8',
scam: '\ud83d\udeab',
};
function formatResult(phoneNumber, result) {
const emoji = VERDICT_EMOJI[result.verdict] || '\u2753';
const verdictLabel = result.verdict.replace('_', ' ').toUpperCase();
const confidence = Math.round(result.confidence * 100);
let message = `${emoji} *ScamVerify\u2122 Report*\n\n`;
message += `*Number:* ${phoneNumber}\n`;
message += `*Verdict:* ${verdictLabel}\n`;
message += `*Risk Score:* ${result.risk_score}/100\n`;
message += `*Confidence:* ${confidence}%\n\n`;
message += `${result.explanation}\n\n`;
message += `_Powered by scamverify.ai_`;
return message;
}
function formatError(phoneNumber, errorMessage) {
return `\u274c Could not check *${phoneNumber}*\n\n${errorMessage}\n\nPlease try again later.`;
}
function formatHelp() {
return (
`*ScamVerify\u2122 Bot*\n\n` +
`Send me a phone number and I'll check if it's associated with scams.\n\n` +
`*How to use:*\n` +
`Just send a US phone number in any format:\n` +
`\u2022 (555) 123-4567\n` +
`\u2022 555-123-4567\n` +
`\u2022 5551234567\n` +
`\u2022 +15551234567\n\n` +
`_Powered by scamverify.ai_`
);
}
module.exports = { formatResult, formatError, formatHelp };Build the webhook server
Create the Express server that handles webhook verification, receives incoming messages, and sends replies.
// server.js
require('dotenv').config();
const express = require('express');
const { ScamVerifyClient } = require('./scamverify');
const { sendMessage } = require('./whatsapp');
const { formatResult, formatError, formatHelp } = require('./formatter');
const app = express();
app.use(express.json());
const scamverify = new ScamVerifyClient(process.env.SCAMVERIFY_API_KEY);
const VERIFY_TOKEN = process.env.WEBHOOK_VERIFY_TOKEN;
// Extract a phone number from message text
function extractPhone(text) {
const cleaned = text.trim();
// Check for help commands
if (['help', 'hi', 'hello', 'start', 'menu'].includes(cleaned.toLowerCase())) {
return { type: 'help' };
}
// Extract digits
const digits = cleaned.replace(/\D/g, '');
if (digits.length === 10) {
return { type: 'phone', number: digits };
}
if (digits.length === 11 && digits.startsWith('1')) {
return { type: 'phone', number: digits };
}
return { type: 'invalid' };
}
// Webhook verification (Meta sends a GET request to verify your endpoint)
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 message handler
app.post('/webhook', async (req, res) => {
// Respond immediately to avoid Meta retries
res.sendStatus(200);
const body = req.body;
if (body.object !== 'whatsapp_business_account') return;
const entries = body.entry || [];
for (const entry of entries) {
const changes = entry.changes || [];
for (const change of changes) {
const messages = change.value?.messages || [];
for (const message of messages) {
if (message.type !== 'text') continue;
const from = message.from; // sender's phone number
const text = message.text?.body || '';
await handleMessage(from, text);
}
}
}
});
async function handleMessage(from, text) {
const parsed = extractPhone(text);
if (parsed.type === 'help') {
await sendMessage(from, formatHelp());
return;
}
if (parsed.type === 'invalid') {
await sendMessage(
from,
'I couldn\'t find a valid US phone number in your message. ' +
'Send a 10-digit phone number, or type *help* for instructions.'
);
return;
}
try {
const result = await scamverify.lookupPhone(parsed.number);
await sendMessage(from, formatResult(parsed.number, result));
} catch (err) {
console.error(`Lookup error for ${parsed.number}:`, err.message);
await sendMessage(from, formatError(parsed.number, err.message));
}
}
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`WhatsApp bot running on port ${PORT}`);
});Run and test
Start the server and expose it with ngrok:
node server.js
# In another terminal:
ngrok http 3000Copy the ngrok HTTPS URL and paste it into your Meta app's webhook configuration:
- Callback URL:
https://your-ngrok-url/webhook - Verify Token:
scamverify_webhook_secret
Click Verify and Save. Meta will send a GET request to verify the endpoint.
Now send a message to your WhatsApp test number:
(555) 867-5309The bot should reply with a formatted risk assessment within a few seconds.
Try sending help to see the instructions message, and try sending hello world to see the invalid input response.
Deploy to production
For production, you need a permanent access token and a verified WhatsApp Business number.
- Permanent token: Create a System User in Meta Business Manager, generate a token with
whatsapp_business_messagingpermission - Business verification: Complete Meta's business verification process
- Phone number: Register a real phone number (instead of the test number)
Deploy the server to Railway, Render, or any Node.js hosting:
FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]Update the webhook URL in Meta to point to your production domain.
WhatsApp Business API pricing: Meta charges per conversation (approximately $0.005 to $0.08 depending on region and conversation type). Budget for this alongside your ScamVerify™ API costs.
Next Steps
- Add URL checking by detecting
http://andhttps://in messages - Support bulk checks by accepting comma-separated phone numbers
- Add message templates for proactive scam alerts
- Track usage with a simple SQLite database to monitor popular queries