Use cases
End-to-end recipes for the flows most e-commerce and SaaS teams build first. All use the small client from the Libraries guide.
OTP / login codes
Deliver a one-time code over WhatsApp. You generate, store, and verify the code — the API only delivers it. Use an AUTHENTICATION template such as otp_code with body Your verification code is {{1}}.
import { mywa } from './mywa.js';
async function sendOtp(phone) {
const code = String(Math.floor(100000 + Math.random() * 900000));
await saveCode(phone, code, { ttlSeconds: 300 }); // your storage
await mywa.post('/messages', {
to: phone,
type: 'template',
template: { name: 'otp_code', variables: [code] },
});
}
async function verifyOtp(phone, entered) {
const code = await readCode(phone);
return code && code === entered;
}Abandoned cart
When a cart goes stale, upsert the shopper and send a MARKETING template like Hi {{1}}, you left {{2}} in your cart — finish here: {{3}}. Replies land in your inbox and open a 24-hour window for follow-up.
import { mywa } from './mywa.js';
async function cartReminder(shopper, cart) {
// Keep the customer record fresh and attach cart context
await mywa.post('/contacts', {
phone: shopper.phone,
name: shopper.name,
attributes: { lastCartValue: cart.total, lastCartAt: new Date().toISOString() },
});
await mywa.post('/messages', {
to: shopper.phone,
type: 'template',
template: {
name: 'cart_reminder',
variables: [shopper.firstName, cart.summary, cart.checkoutUrl],
},
});
}Order & shipping updates
Fire a UTILITY template from your order webhook when status changes. If the customer recently messaged you, a plain text send works too and is cheaper.
// Called from your "order.shipped" webhook handler
async function onOrderShipped(order) {
await mywa.post('/messages', {
to: order.customerPhone,
type: 'template',
template: {
name: 'order_shipped',
variables: [order.customerName, order.number, order.trackingUrl],
},
});
}Order placed → follow-up automation
Build the follow-up once in the automation builder — a thank-you, a delivery check, a review request — and end it with a “Goal reached” step. Then start it from your order handler, passing order details as vars so its messages can say {{vars.hook.orderId}}. Save the runId with the order and check later whether the run reached its goal.
import { mywa } from './mywa.js';
const FOLLOW_UP = 'wf_9f2'; // from GET /automations
// Called from your "order.created" handler
async function onOrderPlaced(order) {
try {
const { runId } = await mywa.post(`/automations/${FOLLOW_UP}/enroll`, {
phone: order.customerPhone,
name: order.customerName,
attributes: { lastOrderAt: order.createdAt },
vars: { orderId: order.number, amount: order.total },
});
await saveRunId(order.id, runId); // your storage
} catch (e) {
// reentry / opted_out never succeed — don't retry them
if (/MYWA 409 (reentry|opted_out)/.test(e.message)) return;
throw e;
}
}
// A few days later, e.g. from a daily job
async function didFollowUpWork(order) {
const run = await mywa.get(`/automation-runs/${order.runId}`);
return run.goal !== null; // true once the "Goal reached" step ran
}202 as soon as the run is queued. A run that is still waiting for a reply or a timer has finishedAt: null — check again later rather than treating it as failed.Getting leads back out?