Verify WhatsApp quickstart Disabled
Verify a phone number by sending a one-time passcode over WhatsApp instead of SMS. WhatsApp is the strongest option in markets where it is the dominant messenger: delivery is over data rather than the carrier SMS rail, so it can be cheaper and more reliable internationally. The API contract below is committed and stable; the disabled-for-sending state is called out where it applies.
Setup time: 2-4 weeks$0.03 per successful verification
WhatsApp is currently disabled for sending. POST /v1/verify with channel: "whatsapp" returns 422 with { "error": "channel_not_available", "channel": "whatsapp" }. Every request and response shape on this page is the committed contract; the check call and read endpoints work identically across channels.
When to choose WhatsApp
Choose WhatsApp when your users are concentrated in regions where it is the primary messaging app, when international SMS costs or deliverability are a problem, or when you want a branded, in-app verification experience. For domestic phone verification with the widest reach and no external review, start with SMS; use voice as an accessibility and landline fallback.
Prerequisites
- A message.com workspace with Verify activated and a positive credit balance. See Get started with Verify.
- A Verify Service with
whatsappin itsenabledChannels. - An API key with the
verifyscope (mk_live_...). See Authentication. - The destination country in the service's
allowedCountriesallowlist: WhatsApp is a phone channel and subject to the same allowlist as SMS. - A Meta Business Manager verification and an approved WhatsApp message template. Both are gated by Meta's own review timeline, which is why setup runs 2-4 weeks. See setup times.
Full verification walk
Three steps every time: start a verification, the user receives the code in WhatsApp, then check the code they typed. The request and response shapes are identical to every other channel.
1. Send the code
Call POST /v1/verify with the phone number as to and "whatsapp" as the channel. E.164 format is preferred.
curl -X POST https://api.message.com/v1/verify \
-H 'Authorization: Bearer mk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"to": "+14155550142",
"channel": "whatsapp"
}'curl -X POST https://api.message.com/v1/verify ^
-H "Authorization: Bearer mk_live_..." ^
-H "Content-Type: application/json" ^
-d "{\"to\": \"+14155550142\", \"channel\": \"whatsapp\"}"const start = await fetch('https://api.message.com/v1/verify', {
method: 'POST',
headers: {
Authorization: 'Bearer mk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: '+14155550142', channel: 'whatsapp' }),
});
const verification = await start.json();
// When live:
// { id, status: 'pending', channel: 'whatsapp', hint: '•••0142', dispatch: 'sent', expiresAt }
// Today:
// { error: 'channel_not_available', channel: 'whatsapp' } (HTTP 422)import requests
start = requests.post(
"https://api.message.com/v1/verify",
headers={"Authorization": "Bearer mk_live_..."},
json={"to": "+14155550142", "channel": "whatsapp"},
)
verification = start.json()
# When live:
# { "id": ..., "status": "pending", "channel": "whatsapp", "hint": "•••0142", "dispatch": "sent", "expiresAt": ... }
# Today:
# { "error": "channel_not_available", "channel": "whatsapp" } (HTTP 422)When WhatsApp is live, a successful start returns 201 with the same flat body every channel uses:
{
"id": "1f6a2e4c-9d3b-4a1e-8f2c-6b7a9e0d5c3f",
"status": "pending",
"channel": "whatsapp",
"hint": "•••0142",
"dispatch": "sent",
"expiresAt": "2026-07-25T18:14:11.000Z"
}Today, the same call returns 422:
{
"error": "channel_not_available",
"channel": "whatsapp"
}2. User receives the code
The user receives the code as a WhatsApp message from your business account. The message body is rendered from the WhatsApp message template you get approved with Meta during setup; the service friendlyName is the brand shown as the sender.
3. Check the code
The check call is channel-agnostic and works today. When the user types the code, send it back with the id from step 1.
curl -X POST https://api.message.com/v1/verify/check \
-H 'Authorization: Bearer mk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"id": "1f6a2e4c-9d3b-4a1e-8f2c-6b7a9e0d5c3f",
"code": "482913"
}'curl -X POST https://api.message.com/v1/verify/check ^
-H "Authorization: Bearer mk_live_..." ^
-H "Content-Type: application/json" ^
-d "{\"id\": \"1f6a2e4c-9d3b-4a1e-8f2c-6b7a9e0d5c3f\", \"code\": \"482913\"}"const check = await fetch('https://api.message.com/v1/verify/check', {
method: 'POST',
headers: {
Authorization: 'Bearer mk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: verification.id, code: userSubmittedCode }),
});
const result = await check.json();
// success: { id: '1f6a2e4c-...', verified: true }
// failure: { id: '1f6a2e4c-...', verified: false, reason: 'bad_code' }check = requests.post(
"https://api.message.com/v1/verify/check",
headers={"Authorization": "Bearer mk_live_..."},
json={"id": verification["id"], "code": user_submitted_code},
)
result = check.json()
# success: {"id": "1f6a2e4c-...", "verified": True}
# failure: {"id": "1f6a2e4c-...", "verified": False, "reason": "bad_code"}A correct code returns 200 with { id, verified: true } and bills $0.03. A wrong code also returns 200, with verified: false and a reason. Read the verified field, not the HTTP status.
Fraud interactions
WhatsApp is a phone channel and inherits the same fraud controls as SMS. When WhatsApp is live these apply on every send:
- Country allowlist. A number whose country is not in the service's
allowedCountriesreturns403 country_not_allowed. - Per-destination throttle. More than 3 sends to the same number in 60 seconds returns
429 rate_limitedwithscope: "destination". - Per-service velocity. The per-minute and per-day caps return
429 rate_limitedwithscope: "workspace". - Attempt cap. Five wrong codes lock the verification with
too_many_attempts.
Country allowlists, velocity caps, and destination throttles are covered in depth in the Verify fraud guide.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
422 channel_not_available | Expected today: WhatsApp is disabled for sending. Use SMS or email now. Do not retry this call for the same channel; it will not become available mid-session. |
400 channel_not_enabled | The resolved service does not list whatsapp in enabledChannels. This is a per-service setting, distinct from the platform-wide channel_not_available. |
403 country_not_allowed (when live) | The number's country is not in the service's allowedCountries. Add the country to the allowlist. |
400 invalid_number | The to value fails the phone-shape check. Send digits, optionally with a leading +. |
Check returns reason: "expired" | The code is past expiresAt. Start a new verification. There is no separate resend endpoint. |
Every reason above, with its exact firing condition, is on the Verify API error codes page.