Documentation

Send WhatsApp OTPs with one API call

WA Gateway sends verification codes from your own WhatsApp number to your users. Link your phone once, create an API key, and send a code with a single HTTP request.

BASE URLhttps://ars-whatsapp-api.onrender.com
  • One endpoint. POST /send-otp is all you need. No templates to register, no approval process.
  • Your own number. Messages come from the WhatsApp number you link, not an unknown sender.
  • One-tap copy. Every message has a Copy Code button, so users never retype the code.
POST /send-otp
curl -X POST "https://ars-whatsapp-api.onrender.com/send-otp" \
  -H "x-api-key: $WA_GATEWAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+8801712345678",
    "otp": "482910"
  }'
Response
200 OK
{
  "statusCode": 200,
  "success": true,
  "message": "OTP sent successfully",
  "data": {
    "messageId": "3EB0A1F4C29D7E83B6105F",
    "recipientJid": "8801712345678@s.whatsapp.net"
  }
}
Getting started

How it works

When you link your phone, WA Gateway becomes one of its Linked Devices, just like WhatsApp Web. Each OTP you send goes out from that number.

  1. STEP 1
    Your server

    Generates a code and calls POST /send-otp

  2. STEP 2
    WA Gateway

    Checks your API key and formats the number

  3. STEP 3
    Your WhatsApp

    Your linked phone sends the message

  4. STEP 4
    User's phone

    Receives the code with a Copy Code button

YOUR APP OWNS THE CODE
WA Gateway delivers the code but never stores or checks it. Your app generates the code, saves it, and verifies what the user types. The Verify OTP codes guide shows the whole flow.
Getting started

Quickstart

Send your first OTP in about two minutes.

  1. 1
    Create an account

    Sign up and verify your email.

  2. 2
    Link your WhatsApp

    In the dashboard, open WhatsApp Device. On your phone, open WhatsApp → Settings → Linked Devices → Link a device and scan the QR code. The status turns green when it is connected.

  3. 3
    Create an API key

    Open API Keys → Generate. Save the key in an environment variable on your server, not in your code.

    # Keep this file out of git
    WA_GATEWAY_KEY=_ars_2k03_a9f1b8c2d3e4f5061728394a5b6c7d8e9f0123456789abcd
  4. 4
    Send an OTP

    Replace the number with your own and run it.

    curl -X POST "https://ars-whatsapp-api.onrender.com/send-otp" \
      -H "x-api-key: $WA_GATEWAY_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "phoneNumber": "+8801712345678",
        "otp": "482910"
      }'
  5. 5
    Check your phone

    The code arrives on WhatsApp within a few seconds. The request also shows up in Message Logs in the dashboard.

NO CODE? USE THE MESSAGE TESTER
Dashboard → Message Tester sends a real OTP from the browser, so you can see the full flow before writing any code.
Basics

Authentication

Every request needs an API key. Send it in the x-api-key header, or as Authorization: Bearer <key>. Both work the same way.

Keys start with _ars_2k03_ followed by 48 characters. You can create several keys, for example one per app or environment, and they all send from your linked number.

Revoking a key

Delete a key in API Keys and it stops working right away. Requests that use it get 401 Unauthorized.

KEEP KEYS ON YOUR SERVER
Anyone with your key can send messages from your number. Never put it in browser or mobile app code, and never commit it to git. If a key leaks, delete it and create a new one.
Request headers
x-api-key: _ars_2k03_a9f1b8c2d3e4f5061728394a5b6c7d8e9f0123456789abcd
Basics

Phone number format

Send the number with its country code. Spaces, dashes, brackets and + are removed for you, and Bangladeshi local numbers that start with 0 get 880 added. After cleaning, the number must have 10 to 15 digits.

YOU SENDSENT TORESULT
+88017123456788801712345678✓ Works
017123456788801712345678✓ Works (Bangladeshi local format)
+880 1712-3456788801712345678✓ Works
+1415555012314155550123✓ Works (any country)
12345—✗ 400: invalid number (needs 10–15 digits)
Basics

Rate limits

Each IP address can make 30 requests per minute. Extra requests get 429 Too Many Requests. Wait a minute, then try again.

PROTECT YOUR OWN ENDPOINT TOO
Limit how often one user can ask for a code in your app (for example, once every 60 seconds). Otherwise one person can use up your limit and spam numbers from your WhatsApp.
Response
429 TOO MANY REQUESTS
{
  "statusCode": 429,
  "message": "ThrottlerException: Too Many Requests"
}
API reference

Send an OTP

POSThttps://ars-whatsapp-api.onrender.com/send-otp

Sends a verification code to a WhatsApp number. The user gets a message with the code and a one-tap Copy Code button. A 200 response means WhatsApp accepted the message.

Headers

x-api-keystringREQUIRED
Your API key. You can use Authorization: Bearer <key> instead.
Content-TypestringREQUIRED
application/json

Body parameters

phoneNumberstringREQUIRED
The recipient's WhatsApp number with country code. See phone number format.
otpstringREQUIRED
The code to send, as a string (for example "482910"). It is shown in bold and copied by the button. Your app generates and checks it.

Other fields in the body are ignored.

Response fields

statusCodenumber
Always 200 on success.
successboolean
Always true on success.
messagestring
"OTP sent successfully"
data.messageIdstring
WhatsApp's ID for this message. Save it if you want to match the request with the entry in Message Logs.
data.recipientJidstring
The WhatsApp address the code was sent to, for example 8801712345678@s.whatsapp.net. Shows which number was used after formatting.
POST /send-otp
curl -X POST "https://ars-whatsapp-api.onrender.com/send-otp" \
  -H "x-api-key: $WA_GATEWAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+8801712345678",
    "otp": "482910"
  }'
Response
200 OK
{
  "statusCode": 200,
  "success": true,
  "message": "OTP sent successfully",
  "data": {
    "messageId": "3EB0A1F4C29D7E83B6105F",
    "recipientJid": "8801712345678@s.whatsapp.net"
  }
}
Guide

Verify OTP codes

WA Gateway only delivers the code, so checking it is up to your app. A safe flow has two parts:

  1. Send: generate a random 6-digit code, save a hash of it with an expiry time, then call POST /send-otp.
  2. Verify: when the user types the code, compare its hash with the saved one. Delete it after success, after it expires, or after too many wrong tries.

Why these rules

Random codessecurity
Use crypto.randomInt or secrets, not Math.random, so codes cannot be guessed.
Store a hashsecurity
If your database leaks, the codes inside it are useless.
5-minute expirysecurity
Matches the "expires in 5 minutes" text users see. WA Gateway does not enforce it; your app must.
5 attempts maxsecurity
Stops someone from trying all 1,000,000 codes.
Single usesecurity
Delete the code once it is used so it cannot be reused.
otp.js / otp.py
import crypto from "node:crypto";

// Use Redis or your database in production.
const codes = new Map();

const hash = (code) => crypto.createHash("sha256").update(code).digest("hex");

// 1. Generate a code, remember it, and send it.
export async function sendLoginCode(phoneNumber) {
  const otp = crypto.randomInt(100000, 1000000).toString();

  codes.set(phoneNumber, {
    hash: hash(otp),
    expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
    attempts: 0,
  });

  const response = await fetch("https://ars-whatsapp-api.onrender.com/send-otp", {
    method: "POST",
    headers: {
      "x-api-key": process.env.WA_GATEWAY_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ phoneNumber, otp }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message);
  }
}

// 2. Check the code the user typed in.
export function verifyLoginCode(phoneNumber, code) {
  const entry = codes.get(phoneNumber);
  if (!entry || Date.now() > entry.expiresAt) return false;

  entry.attempts += 1;
  if (entry.attempts > 5) {
    codes.delete(phoneNumber); // too many wrong tries
    return false;
  }

  const ok = crypto.timingSafeEqual(
    Buffer.from(hash(code)),
    Buffer.from(entry.hash),
  );
  if (ok) codes.delete(phoneNumber); // a code works only once
  return ok;
}
Guide

Handle errors

Errors fall into two groups, and your app should treat them differently:

400 · 429 → THE USER CAN FIX IT

Show a friendly message: "Check your number" or "Please wait a minute and try again".

401 · 503 → YOU NEED TO FIX IT

Your key is wrong or your WhatsApp is disconnected. Retrying will not help. Alert your team and show the user a general "try again later" message.

SET A TIMEOUT
Sending usually takes a second or two. Set a timeout of about 15 seconds so a slow request never blocks your app.
Handling each status
const response = await fetch("https://ars-whatsapp-api.onrender.com/send-otp", {
  method: "POST",
  headers: {
    "x-api-key": process.env.WA_GATEWAY_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ phoneNumber, otp }),
});

const body = await response.json();
if (response.ok) return body.data.messageId;

// Validation errors return a list of messages, others a single string.
const message = Array.isArray(body.message) ? body.message.join(", ") : body.message;

switch (response.status) {
  case 400:
    // Bad number or not on WhatsApp: ask the user to check their number.
    throw new UserInputError(message);
  case 429:
    // Too many requests: wait a minute before trying again.
    throw new RetryLaterError(message);
  case 401:
  case 503:
    // Wrong key, or your WhatsApp is disconnected: alert your team.
    // Retrying will not help until it is fixed.
    notifyTeam(message);
    throw new Error("Could not send the code. Please try again later.");
  default:
    throw new Error(message);
}
Reference

Error codes

Errors return a non-2xx status and a JSON body. message explains what went wrong; for missing or invalid fields it is a list of messages.

STATUSMEANINGWHAT TO DO
400phoneNumber or otp is missing or not a stringSend both fields as strings.
400Invalid phone numberUse 10–15 digits with the country code.
400Number is not registered on WhatsAppAsk the user for a WhatsApp number.
401API key missing, invalid or revokedCheck the header and that the key still exists.
429More than 30 requests in a minuteWait a minute, then retry.
503Your WhatsApp is not connectedDashboard → WhatsApp Device → scan the QR again.
Error response
{
  "message": [
    "OTP code is required"
  ],
  "error": "Bad Request",
  "statusCode": 400
}
Reference

What users receive

This is exactly how the message looks in the user's WhatsApp. Try the Copy Code button: it copies the code, just like on a real phone.

  1. Sender

    Your linked WhatsApp number. Users see your name instead if they saved you as a contact.

  2. The code

    Your otp value, in bold, followed by a short security warning.

  3. Footer and time

    A fixed note that the code expires in 5 minutes, and when it arrived.

  4. Copy Code button

    One tap copies the code to the clipboard, so users can paste it into your app without typing.

ABOUT "EXPIRES IN 5 MINUTES"
This footer text is fixed. WA Gateway does not expire codes; your app decides how long a code stays valid. Use 5 minutes so the message stays true.
10:185G ▮▮▮ 86%
+880 1711-000000
online
Today
Messages and calls are end-to-end encrypted. No one outside of this chat can read them.
482910 is your verification code.

For your security, do not share this code.
This code expires in 5 minutes10:18 am
Reference

Message logs

Every successful send is recorded in the dashboard under Message Logs, with the recipient, the time, the messageId and the status. You can search by phone number or message ID. Use it to check whether a code was sent when a user says they did not get one. The last 100 messages are shown.

Reference

Best practices

  • ✓ Call from your server

    Keep your API key in an environment variable on the server.

  • ✓ Limit requests per user

    Allow one code per user every 60 seconds to stop abuse.

  • ✓ Expire codes after 5 minutes

    Matches the message users see.

  • ✓ Use one key per app

    If one leaks, you revoke only that key.

  • ✓ Watch for 503

    It means your phone was unlinked. Relink it quickly so logins keep working.

  • ✓ Keep the device linked

    If you log out of WhatsApp or remove the linked device, sending stops with 503.

Reference

FAQ

Can I change the message text?+

Not yet. Every OTP uses the same template shown in "What users receive". Only the code changes.

Can I send to numbers outside Bangladesh?+

Yes. Send the number with its country code, for example +14155550123. Any number registered on WhatsApp works.

Can I link more than one WhatsApp number?+

No. One account links exactly one WhatsApp number, and every API key on the account sends from it. Use a separate account for a second number.

Does WA Gateway check if the code the user typed is correct?+

No. WA Gateway only delivers the code. Your app creates it, stores it and checks it. See "Verify OTP codes" for a complete example.

Is there a test mode?+

No. Every request sends a real WhatsApp message. Use your own number while testing, or the Message Tester in the dashboard.

Can I call the API from my website or mobile app directly?+

No. Browsers are blocked by CORS, and putting your key in client code would let anyone send messages from your number. Always call it from your server.

Next steps