message.comDevelopers

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.

1Start a verificationYour backend calls 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.
2User receives the codeThe user gets a text, email, phone call, push notification, or opens their authenticator app, depending on the channel. They type the code into your app's UI.
3Check the codeYour backend calls 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
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"
  }'
JavaScript
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 }
Python
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
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"
  }'
JavaScript
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 }
Python
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.

SMSLive
EmailLive
VoiceSetup 1 hour
TOTPSetup 1 hour
PushSetup 1 day
WhatsAppSetup 2-4 weeks
Silent Network AuthSetup 2-4 weeks

Full channel behavior, setup time, and prerequisites: see Verify channel setup times.

Reference and guides

API referenceEndpoints, params, errorsFull request and response shapes for /v1/verify, /v1/verify/check, rate-limit semantics, and Verify Services.
Fraud preventionWhat we block by defaultCountry allowlists, per-service velocity caps, workspace ceilings, and attempt limits, documented honestly instead of hidden.

Channel status legend

Livedispatches today.  Planned API shape shipped, dispatch not wired to a live carrier yet.

Next steps