// Endpoints
One-time passwords
Issue a login code over WhatsApp and verify it.
Two calls give you WhatsApp login codes without building any of the hard parts. We generate the code, store only a hash of it, expire it and limit the guesses. Your server never sees the code, so it can never leak it.
- You need an approved authentication template. Create one under Settings → Templates.
- Use separate keys if you can: sending needs
messages:send, checking needsotp:verify. A marketing tool holding a send key cannot confirm a login. - Codes are not blocked by opt-out. Someone who replied STOP to promotions can still sign in.
POST
/api/v1/otp/sendIssue a one-time password
Scope: messages:send
curl -X POST https://whatsapp.esendblue.com/api/v1/otp/send \
-H "Authorization: Bearer wacrm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "+919876543210", "template_name": "login_code", "language": "en", "purpose": "login" }'| Field | Default | Notes |
|---|---|---|
to | — | Required, E.164 |
template_name | — | Required. An approved authentication template |
language | — | The template language, e.g. en |
purpose | — | Keeps flows apart: a checkout code does not cancel a login code |
code_length | 6 | 4 to 8 digits |
expires_in_seconds | 300 | 60 to 900 |
max_attempts | 5 | 1 to 10 wrong guesses before the code dies |
Out-of-range values are clamped rather than rejected, so a bad setting never breaks a login. A new code replaces the previous one for the same number and purpose. Resends are limited to one per 30 seconds and five per hour per number (otp_resend_too_soon, otp_rate_limited).
POST
/api/v1/otp/verifyVerify a one-time password
Scope: otp:verify
curl -X POST https://whatsapp.esendblue.com/api/v1/otp/verify \
-H "Authorization: Bearer wacrm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "+919876543210", "code": "483920", "purpose": "login" }'There is no verified: false
Every failure is an error response. Code that only checks for a 200 cannot mistake a wrong code for a successful login.
| code | Status | Meaning |
|---|---|---|
otp_not_found | 400 | No live code for this number and purpose |
otp_expired | 400 | The code timed out. Send a new one |
otp_invalid | 400 | Wrong code. The attempt was counted |
otp_already_used | 400 | Right code, but it was already used |
otp_attempts_exceeded | 429 | Too many wrong guesses. Send a new code |
Typical login#
const API = "https://whatsapp.esendblue.com/api/v1";
const headers = (key) => ({
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
});
// 1. User typed their number.
await fetch(`${API}/otp/send`, {
method: "POST",
headers: headers(process.env.WHATSAPP_SEND_KEY),
body: JSON.stringify({ to: phone, template_name: "login_code", language: "en", purpose: "login" }),
});
// 2. User typed the code they received.
const res = await fetch(`${API}/otp/verify`, {
method: "POST",
headers: headers(process.env.WHATSAPP_VERIFY_KEY),
body: JSON.stringify({ to: phone, code, purpose: "login" }),
});
if (!res.ok) throw new Error((await res.json()).error.code);
// Signed in.