WhatsApp Business API for purchase decision nurture
This scenario sends a prospective customer a personalised WhatsApp template while they are still deciding.

Use case overview
This scenario sends a prospective customer a personalised WhatsApp template while they are still deciding. The message includes their name, the product or service under consideration, and a helpful next-step hint. Two quick-reply buttons let them ask a question or signal they are ready to buy; taps arrive via webhook for routing to sales or automated follow-up.
Template example
Hi {{1}}! We're here to help you decide on {{2}}. {{3}} — tap a button below if you have questions or you're ready to move forward.
[Ask a question]
Variables and purpose
{{1}}— customer or prospect name{{2}}— name of the product or service under consideration{{3}}— helpful next-step hint or nurture touchpoint (e.g. comparison guide, FAQ, offer to schedule a call)
Filled-in example
Hi Anna! We're here to help you decide on Premium Plan subscription. Our team prepared a comparison guide and can answer pricing questions — tap a button below if you have questions or you're ready to move forward.
[Ask a question]
When to use it
- b2b and services
- e-commerce and saas
- agencies and small teams
Business value
- detect a nurture touchpoint trigger for a prospect still in the decision stage
- resolve customer phone, product or service context, and the next-step hint
- send a personalised nurture template with two quick-reply engagement buttons
- receive button taps via webhook and route to sales or automated follow-up
- log delivery and engagement for CRM pipeline until buy or no-buy decision
Workflow
- CRM or the sales pipeline detects that a nurture touchpoint is due for a prospect still in the decision stage.
- Customer phone, product or service context, and the next-step hint are taken from CRM or the pipeline record.
- A personalised template is sent with three body variables and two quick-reply buttons for engagement.
- The prospect taps «Ask a question» or «Ready to buy» — the event arrives via webhook and follow-up is routed accordingly.
- Delivery and engagement are logged in CRM until the prospect buys, declines, or exits the nurture track.
- Delivery progress is reported asynchronously — typically
sent, thendelivered(or failed/undelivered). - Your system receives status via webhook (
hooks[]) or pollsGET …/hookInfo?messageId=<id>and handles failures if needed.

Technical implementation
Prerequisites
- An approved WhatsApp template with three body variables and two quick-reply buttons («Ask a question», «Ready to buy»).
- A 1MSG account with access to the template send API and a configured webhook for incoming messages.
- Integration with CRM or a sales pipeline to schedule touchpoints and record engagement until a decision.
- The generated
handleIncomingMessagematches inbound text to template button labels — a starting point; replace with your production router.
Code examples
#!/usr/bin/env node
// === Configuration (replace "___" placeholders) ===
const API_BASE_URL = "https://api.1msg.io"; // production 1MSG API base URL
const CHANNEL_ID = "___"; // channel ID from 1MSG dashboard
const API_TOKEN = "___"; // channel JWT token (Bearer)
const TEMPLATE_NAME = "___"; // approved template name
const TEMPLATE_NAMESPACE = "___"; // template namespace (422 without it)
const TEMPLATE_LANGUAGE = "___"; // template language code, e.g. "en"
// === Test data ===
const TEST_PHONE = "___"; // client phone in international format
const TEST_CUSTOMERNAME = "___"; // {{1}} customer name
const TEST_PRODUCTORSERVICENAME = "___"; // {{2}} product or service name
const TEST_NEXTSTEP = "___"; // {{3}} next step
function normalizePhone(phone) {
return String(phone).replace(/\D/g, "");
}
function assertConfigured(values) {
for (const [key, value] of Object.entries(values)) {
if (value === "___" || value === "" || value === undefined || value === null) {
throw new Error(`Missing configuration value: ${key}`);
}
}
}
async function sendTemplateMessage({ phone, customerName, productOrServiceName, nextStep }) {
assertConfigured({
CHANNEL_ID,
API_TOKEN,
TEMPLATE_NAME,
TEMPLATE_NAMESPACE,
TEMPLATE_LANGUAGE,
phone,
customerName,
productOrServiceName,
nextStep,
});
const url = `${API_BASE_URL}/${CHANNEL_ID}/sendTemplate`;
// params carries body and button blocks (dynamic buttons).
const requestBody = {
phone: normalizePhone(phone),
template: TEMPLATE_NAME,
namespace: TEMPLATE_NAMESPACE,
language: {
policy: "deterministic",
code: TEMPLATE_LANGUAGE,
},
params: [
{
type: "body",
parameters: [
{ type: "text", text: String(customerName) }, // {{1}} customer name
{ type: "text", text: String(productOrServiceName) }, // {{2}} product or service name
{ type: "text", text: String(nextStep) }, // {{3}} next step
],
},
],
};
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_TOKEN}`,
},
body: JSON.stringify(requestBody),
});
const raw = await res.text();
let data;
try {
data = JSON.parse(raw);
} catch {
data = null;
}
if (!res.ok || !data || data.sent !== true) {
console.error("Send failed. API response:");
console.error(raw);
process.exit(1);
}
console.log("Message sent to client.");
console.log("API response:", raw);
return data;
}
function handleIncomingMessage(message) {
const text = (message && (message.text || message.body || "")).trim();
// Meta quick_reply label (from template.txt) → route key (code_contract.ref) "ask_question"
if (text === "Ask a question") {
console.log("Reply received: ask_question");
return { status: "ask_question" };
}
// Meta quick_reply label (from template.txt) → route key (code_contract.ref) "ready_to_buy"
if (text === "Ready to buy") {
console.log("Reply received: ready_to_buy");
return { status: "ready_to_buy" };
}
console.log("Free-text reply — handle separately.");
return { status: "other" };
}
if (require.main === module) {
sendTemplateMessage({
phone: TEST_PHONE,
customerName: TEST_CUSTOMERNAME,
productOrServiceName: TEST_PRODUCTORSERVICENAME,
nextStep: TEST_NEXTSTEP,
}).catch((err) => {
console.error("Execution failed:", err.message);
process.exit(1);
});
}
module.exports = { sendTemplateMessage, handleIncomingMessage };
Immediate API response (synchronous)
- HTTP 2xx and JSON
"sent": truemean 1MSG accepted the message for sending — not that it already reached the customer's phone. - Save the `id` field from the response (value looks like
wamid.…). Use it to correlate delivery callbacks or polling. - The response may also include
messageanddescription— informational only.
Delivery status (asynchronous)
- Register a webhook (
POST …/webhook) so 1MSG POSTs delivery updates to your HTTPS endpoint in a separate `hooks[]` payload (sent,delivered,read, or failed/undelivered when applicable). - Optionally poll:
GET {base}/{channel}/hookInfo?messageId=<id from sendTemplate>. - In practice, delivery often completes within a few seconds — but that is not guaranteed by the API contract.
Common errors
- Invalid or non-normalized phone number
- Unapproved or missing template name / namespace
- No customer opt-in for WhatsApp business messages
- Template variable count mismatch (422 from API)
- Delivery failure — check status webhook and retry policy
FAQ
- Do I need an approved template? Yes — cold-start WhatsApp messages require a Meta-approved template.
- Can I customize the message text? Body variables are dynamic; fixed text and button labels are set in the Meta template.
- How do I check delivery?
sent: trueonly confirms acceptance. Track delivery via webhookhooks[]orGET …/hookInfo?messageId=<id>. - What if the message is not delivered? Log the failed/undelivered hook, verify opt-in and template status, then retry or fall back to another channel.
- Can I connect this to my CRM or backend? Yes — trigger the API call from your platform webhook or event handler.
Related
Build WhatsApp automation in minutes
Use 1MSG to automate this workflow and try it with our free demo.
