Verify API: multi-channel user verification
Fight account takeover and fake signups by verifying that a phone number, email address, or authenticator app actually belongs to the person using it. One API sends a one-time code over SMS, email, voice, WhatsApp, TOTP, push, or Silent Network Auth (SNA), and one endpoint checks it. $0.03 per successful verification, no monthly fee, no minimum commitment.
How it works
Verify is two calls. You never see, store, or transmit the code yourself, which keeps your app out of scope for most OTP-related compliance review.
POST /v1/verify with a destination and a channel. Message.com generates a code, stores only its hash, and dispatches it over the requested channel.POST /v1/verify/check with the verification id and the code the user typed. We consume the code atomically and return verified: true or a reason it failed.Try it
Real endpoints, real shapes. Every request needs a Bearer API key with the verify scope (mk_...). See Get started with Verify for where to find your key.
Start a verification
curl -X POST https://api.message.com/v1/verify \
-H 'Authorization: Bearer mk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"to": "[email protected]",
"channel": "email"
}'const res = await fetch('https://api.message.com/v1/verify', {
method: 'POST',
headers: {
Authorization: 'Bearer mk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: '[email protected]', channel: 'email' }),
});
const verification = await res.json();
// { id, status: 'pending', channel: 'email', hint, dispatch: 'sent', expiresAt }import requests
res = requests.post(
"https://api.message.com/v1/verify",
headers={"Authorization": "Bearer mk_live_..."},
json={"to": "[email protected]", "channel": "email"},
)
verification = res.json()
# { "id": ..., "status": "pending", "channel": "email", "hint": ..., "dispatch": "sent", "expiresAt": ... }Every successful start returns 201 with a flat body: id, status (always pending at creation), channel, a masked hint of the destination, an expiresAt timestamp, and dispatch, which tells you what actually happened on the wire: sent, pending_provisioning (the channel exists but is not wired to a live carrier yet), or error (a real send failure). Creating a verification is never billed, win or lose. The full field list lives in the Verify API reference.
Check the code
curl -X POST https://api.message.com/v1/verify/check \
-H 'Authorization: Bearer mk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"id": "1f6a2e4c-...",
"code": "482913"
}'const res = 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 res.json();
// { id, verified: true }res = requests.post(
"https://api.message.com/v1/verify/check",
headers={"Authorization": "Bearer mk_live_..."},
json={"id": verification["id"], "code": user_submitted_code},
)
result = res.json()
# { "id": ..., "verified": true }A correct code returns 200 with { id, verified: true } and is the only event that bills, at $0.03. An incorrect or expired code returns { id, verified: false, reason } with a machine-readable reason (bad_code, expired, too_many_attempts, already_used). The full error table is in the Verify API error codes reference.
Verifications are never free to probe: sends are throttled per destination and per IP, checks are capped at five attempts, and a consumed or expired code can never be replayed. Full fraud-control detail is in Prevent SMS fraud with Verify.
Channels
Every channel below shares the same two-call shape. Setup time is how long it takes a new workspace to go from signup to a live send on that channel; it is a review and carrier-registration constraint, not a code difference. SMS and email are live today.
Full channel behavior, setup time, and prerequisites: see Verify channel setup times.
Reference and guides
/v1/verify, /v1/verify/check, rate-limit semantics, and Verify Services.Channel status legend
Livedispatches today. Planned API shape shipped, dispatch not wired to a live carrier yet.
Next steps
- Get started with Verify: activate Verify, create a service, mint a key, send your first code.
- Verify channel setup times: how long each channel takes to go live and what is available today.
- Authentication: how
mk_API keys and scopes work platform-wide.