Overview

The MTD API WhatsApp integration lets you:

  • Link a WhatsApp number to a session using a pairing code (no QR scan needed)
  • Check if a session is active before sending
  • Send any text message to any WhatsApp number in a supported country

The typical flow is:

1. Pair your WhatsApp number once → get a session_id
2. Check the session is connected
3. Send messages programmatically forever (until you log out of that device)

Prerequisites

  • MTD API account → api.motadev.xyz
  • Your API key from the Dashboard
  • A WhatsApp account (any number — personal or business)
  • Credits in your account (1/50 credit per send/pair operation)

Supported Countries

Before you send anything, know the supported country codes. Fetch them live:

GET https://api.motadev.xyz/api/whatsapp/countries

This endpoint requires no authentication — call it freely.

Current supported countries:

Code Country Dial Code Local digits Example format
ZA or SA South Africa +27 9 0821234567
NG Nigeria +234 10 08012345678
IN India +91 10 09876543210
PH Philippines +63 10 09171234567
US or CA United States/Canada +1 10 2125551234

The API handles the formatting. You pass the number as the user typed it — with or without a leading zero, with or without + — and the API normalises it to E.164 format (27821234567).


Required Headers

All WhatsApp endpoints (except /countries) require:

X-API-KEY: mtd_key1234567890ABCD
User-Agent: MyApp/1.0
Referer: https://myapp.com

Step 1 — Pair Your WhatsApp Number

This links a WhatsApp number to a session_id of your choosing. You only need to do this once per number. The session persists until you manually log out.

Endpoint

POST /api/whatsapp/pair
Content-Type: application/json

Request Body

{
  "session_id": "my-business-account",
  "phone_number": "0821234567",
  "country": "ZA"
}
Field Required Description
session_id Any string you choose. This is how you reference this WhatsApp account in future API calls.
phone_number The WhatsApp number to link. Accepts any common format.
country Country code from /api/whatsapp/countries.

Response

{
  "success": true,
  "message": "Pairing code issued. Enter it in WhatsApp → Linked Devices → Link with Phone Number.",
  "pairing_code": "ABCD-1234",
  "session_id": "my-business-account",
  "phone_number": "27821234567",
  "credits_remaining": 49
}

What to do with the pairing code

  1. Open WhatsApp on the phone you're linking
  2. Go to Settings → Linked Devices → Link a Device
  3. Tap "Link with phone number instead"
  4. Enter the pairing_code from the response (e.g. ABCD-1234)
  5. Done — your session is now active

You never need to touch that phone again. All future messages go through the API using the session_id.


Step 2 — Verify the Session is Active

Before sending production messages, confirm the session connected successfully.

Endpoint

GET /api/whatsapp/status/{session_id}

Response (connected)

{
  "success": true,
  "session_id": "my-business-account",
  "connected": true,
  "status": "active"
}

Response (not connected)

{
  "success": true,
  "session_id": "my-business-account",
  "connected": false,
  "status": "not_connected"
}

If the status is not_connected, repeat the pairing step — the code may have expired or was entered incorrectly.


Step 3 — Send a Message

Endpoint

POST /api/whatsapp/send
Content-Type: application/json

Request Body

{
  "session_id": "my-business-account",
  "to": "0731234567",
  "country": "ZA",
  "message": "Your OTP is 482910. It expires in 5 minutes."
}
Field Required Description
session_id The session you paired in Step 1
to Recipient's WhatsApp number
country Recipient's country code
message The text to send

Response

{
  "success": true,
  "message": "Message sent successfully.",
  "to": "27731234567",
  "session_id": "my-business-account",
  "credits_remaining": 48
}

Code Examples

Example 1 — OTP Verification System (Python)

A complete OTP flow: generate a code, send it via WhatsApp, verify it in your app.

import requests
import random
import string
import time

API_KEY = "mtd_key1234567890ABCD"
SESSION_ID = "my-business-account"
BASE_URL = "https://api.motadev.xyz"

HEADERS = {
    "X-API-KEY": API_KEY,
    "User-Agent": "OTPService/1.0",
    "Referer": "https://myapp.com",
    "Content-Type": "application/json",
}

# In-memory OTP store (use Redis/DB in production)
_otp_store = {}


def generate_otp(length=6) -> str:
    return "".join(random.choices(string.digits, k=length))


def send_whatsapp_otp(phone: str, country: str) -> dict:
    """Generate and send an OTP to a WhatsApp number."""
    otp = generate_otp()
    expires_at = time.time() + 300  # 5 minutes

    message = (
        f"🔐 *MTD App Verification*\n\n"
        f"Your one-time code is: *{otp}*\n\n"
        f"This code expires in 5 minutes.\n"
        f"Do not share this code with anyone."
    )

    response = requests.post(
        f"{BASE_URL}/api/whatsapp/send",
        json={
            "session_id": SESSION_ID,
            "to": phone,
            "country": country,
            "message": message,
        },
        headers=HEADERS,
        timeout=30,
    )

    data = response.json()
    if data.get("success"):
        # Store OTP keyed by the normalised number the API returned
        normalised = data["to"]
        _otp_store[normalised] = {"otp": otp, "expires_at": expires_at}
        print(f"[OTP] Sent to {normalised}. Credits left: {data['credits_remaining']}")
        return {"success": True, "to": normalised}
    else:
        print(f"[OTP] Failed: {data.get('message')}")
        return {"success": False, "error": data.get("message")}


def verify_otp(phone_e164: str, submitted_code: str) -> bool:
    """Verify a submitted OTP code."""
    record = _otp_store.get(phone_e164)
    if not record:
        return False
    if time.time() > record["expires_at"]:
        del _otp_store[phone_e164]
        return False
    if record["otp"] == submitted_code:
        del _otp_store[phone_e164]
        return True
    return False


# --- Usage ---
result = send_whatsapp_otp("0821234567", "ZA")

if result["success"]:
    # Simulate user entering the OTP
    user_input = input("Enter the OTP you received: ")
    if verify_otp(result["to"], user_input):
        print("✅ OTP verified. User authenticated.")
    else:
        print("❌ Invalid or expired OTP.")

Example 2 — Order Notification + Status Check (Python)

Send a delivery notification and confirm the session is alive first.

import requests

API_KEY = "mtd_key1234567890ABCD"
SESSION_ID = "standby-store"
BASE_URL = "https://api.motadev.xyz"

HEADERS = {
    "X-API-KEY": API_KEY,
    "User-Agent": "StandbyStore/1.0",
    "Referer": "https://standby.co.za",
    "Content-Type": "application/json",
}


def is_session_alive(session_id: str) -> bool:
    resp = requests.get(
        f"{BASE_URL}/api/whatsapp/status/{session_id}",
        headers=HEADERS,
        timeout=15,
    )
    data = resp.json()
    return data.get("connected", False)


def notify_order(customer_phone: str, country: str, order: dict) -> bool:
    if not is_session_alive(SESSION_ID):
        print("[WhatsApp] Session not connected. Message not sent.")
        return False

    message = (
        f"Hey {order['name']}! 👋\n\n"
        f"✅ Your *Standby* order #{order['id']} has been confirmed.\n\n"
        f"📦 Items: {order['items']}\n"
        f"💳 Total: R{order['total']:.2f}\n"
        f"🚚 Estimated delivery: {order['eta']}\n\n"
        f"Track your order: https://standby.co.za/track/{order['id']}\n\n"
        f"Reply *HELP* if you need assistance."
    )

    resp = requests.post(
        f"{BASE_URL}/api/whatsapp/send",
        json={
            "session_id": SESSION_ID,
            "to": customer_phone,
            "country": country,
            "message": message,
        },
        headers=HEADERS,
        timeout=30,
    )

    data = resp.json()
    if data.get("success"):
        print(f"[WhatsApp] Notification sent to {data['to']}")
        return True
    else:
        print(f"[WhatsApp] Send failed: {data.get('message')}")
        return False


# --- Usage ---
order = {
    "id": "ORD-20240601-0042",
    "name": "Sipho",
    "items": "Black Oversized Hoodie x1, Cargo Pants x2",
    "total": 849.00,
    "eta": "2–3 business days",
}

notify_order("0738881234", "ZA", order)

Node.js — Pair + Send in One Script

const axios = require("axios");

const API_KEY = "mtd_key1234567890ABCD";
const BASE_URL = "https://api.motadev.xyz";

const headers = {
  "X-API-KEY": API_KEY,
  "User-Agent": "MyApp/1.0",
  "Referer": "https://myapp.com",
  "Content-Type": "application/json",
};

// ── Step 1: Pair a WhatsApp number ──────────────────────────
async function pairNumber(sessionId, phoneNumber, country) {
  const res = await axios.post(
    `${BASE_URL}/api/whatsapp/pair`,
    { session_id: sessionId, phone_number: phoneNumber, country },
    { headers, timeout: 30000 }
  );
  return res.data;
}

// ── Step 2: Check session status ────────────────────────────
async function checkStatus(sessionId) {
  const res = await axios.get(
    `${BASE_URL}/api/whatsapp/status/${sessionId}`,
    { headers, timeout: 15000 }
  );
  return res.data.connected;
}

// ── Step 3: Send a message ──────────────────────────────────
async function sendMessage(sessionId, to, country, message) {
  const res = await axios.post(
    `${BASE_URL}/api/whatsapp/send`,
    { session_id: sessionId, to, country, message },
    { headers, timeout: 30000 }
  );
  return res.data;
}

// ── Example 1: Full pairing flow ────────────────────────────
(async () => {
  console.log("Pairing WhatsApp number...");
  const pair = await pairNumber("my-app-session", "0821234567", "ZA");

  if (pair.success) {
    console.log(`Got pairing code: ${pair.pairing_code}`);
    console.log("Enter this code in WhatsApp → Linked Devices → Link with Phone Number");
    console.log("Waiting 30 seconds for you to enter the code...");
    await new Promise(r => setTimeout(r, 30000));

    const connected = await checkStatus("my-app-session");
    console.log("Connected:", connected);
  }
})();


// ── Example 2: Send a verification link ─────────────────────
(async () => {
  const SESSION = "my-app-session";
  const connected = await checkStatus(SESSION);

  if (!connected) {
    console.error("Session not active. Run pairing first.");
    return;
  }

  const verifyToken = "abc123xyz";
  const result = await sendMessage(
    SESSION,
    "0761234567",
    "ZA",
    `Welcome to MyApp! 🎉\n\nClick the link below to verify your account:\n\nhttps://myapp.com/verify?token=${verifyToken}\n\nThis link expires in 24 hours.`
  );

  if (result.success) {
    console.log(`Verification link sent to ${result.to}`);
    console.log(`Credits remaining: ${result.credits_remaining}`);
  } else {
    console.error("Failed:", result.message);
  }
})();

Rust — Send OTP

use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;

const API_KEY: &str = "mtd_key1234567890ABCD";
const BASE_URL: &str = "https://api.motadev.xyz";
const SESSION_ID: &str = "rust-app-session";

#[derive(Debug, Deserialize)]
struct WhatsAppResponse {
    success: bool,
    message: String,
    to: Option<String>,
    pairing_code: Option<String>,
    credits_remaining: Option<i64>,
    connected: Option<bool>,
}

fn build_headers() -> reqwest::header::HeaderMap {
    let mut headers = reqwest::header::HeaderMap::new();
    headers.insert("X-API-KEY", API_KEY.parse().unwrap());
    headers.insert("User-Agent", "RustApp/1.0".parse().unwrap());
    headers.insert("Referer", "https://myrustapp.com".parse().unwrap());
    headers.insert("Content-Type", "application/json".parse().unwrap());
    headers
}

fn send_otp(client: &Client, phone: &str, country: &str, otp: &str) -> Result<WhatsAppResponse, Box<dyn std::error::Error>> {
    let message = format!(
        "🔐 Your verification code is: *{}*\n\nExpires in 5 minutes. Do not share this code.",
        otp
    );

    let body = json!({
        "session_id": SESSION_ID,
        "to": phone,
        "country": country,
        "message": message,
    });

    let response = client
        .post(format!("{}/api/whatsapp/send", BASE_URL))
        .headers(build_headers())
        .json(&body)
        .send()?;

    let data: WhatsAppResponse = response.json()?;
    Ok(data)
}

fn check_status(client: &Client) -> Result<bool, Box<dyn std::error::Error>> {
    let response = client
        .get(format!("{}/api/whatsapp/status/{}", BASE_URL, SESSION_ID))
        .headers(build_headers())
        .send()?;

    let data: WhatsAppResponse = response.json()?;
    Ok(data.connected.unwrap_or(false))
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    // Check the session is live before sending
    match check_status(&client) {
        Ok(true) => println!("Session active ✅"),
        Ok(false) => {
            eprintln!("Session not connected. Pair your number first.");
            return Ok(());
        }
        Err(e) => {
            eprintln!("Status check failed: {}", e);
            return Ok(());
        }
    }

    // Send OTP
    let otp = "847291";
    let result = send_otp(&client, "0821234567", "ZA", otp)?;

    if result.success {
        println!("OTP sent to {}", result.to.unwrap_or_default());
        println!("Credits remaining: {}", result.credits_remaining.unwrap_or(0));
    } else {
        eprintln!("Failed: {}", result.message);
    }

    Ok(())
}

Common Message Templates

Copy and adapt these for your use case. WhatsApp renders *bold* and _italic_.

OTP:

🔐 *Verification Code*

Your code is: *{otp}*

Valid for 5 minutes. Never share this with anyone.

Order confirmation:

✅ Order *#{order_id}* confirmed!

Items: {items}
Total: R{total}
Delivery: {eta}

Track: {tracking_url}

Appointment reminder:

📅 *Reminder*

You have an appointment tomorrow at *{time}* with {business}.

Reply *CONFIRM* to confirm or *CANCEL* to cancel.

Password reset link:

🔑 *Password Reset*

Someone requested a password reset for your {app_name} account.

Reset here: {reset_url}

This link expires in 15 minutes. If you didn't request this, ignore this message.

Reconnecting a Dropped Session

If WhatsApp logs out (phone restart, inactive session, etc.), call the connect endpoint to restore the session without re-pairing:

POST /api/whatsapp/connect
Content-Type: application/json

{
  "session_id": "my-business-account"
}

If that doesn't work after a few seconds, do a full re-pair.


Rate Limits & Costs

Endpoint Cost Rate limit
POST /api/whatsapp/pair 1 credit 5 requests / 5 min
GET /api/whatsapp/status/{id} 0 credits 20 requests / min
POST /api/whatsapp/send 1 credit 30 requests / min
POST /api/whatsapp/connect 0 credits 10 requests / min
GET /api/whatsapp/countries 0 credits Unlimited

Troubleshooting

"Unsupported country code" — Use one of the codes from /api/whatsapp/countries. "SA" is accepted as an alias for "ZA".

"Phone number must be X digits" — Strip the country code and leading zero before worrying — the API does that automatically. Just pass the number as the user typed it and let the API normalise it.

Session connects but messages don't deliver — Make sure the recipient's number is on WhatsApp. The API sends to the number, not a verified business profile, so there's no delivery receipt in the response — messages either reach the device or silently fail if the number isn't on WhatsApp.

402 Insufficient credits — Top up at api.motadev.xyz/billing/buy. R99 gets you 500 credits — that's 500 messages.

Pairing code expired — Codes expire quickly (within ~120 seconds of generation). Call /pair again to get a fresh one and enter it immediately.