Get started with Verify
From activation to your first verified user in a few minutes. This walks through the dashboard steps and the two API calls behind them.
1. Activate Verify
Verify is billed separately from chat, tickets, and phone: $0.03 per successful verification, no monthly fee, no minimum commitment. You only pay when a check returns verified: true; a failed check, an expired code, or a verification nobody ever completes costs nothing.
- Sign in at app.message.com and open
Settings → Verify. - Click Activate Verify. This does not start a subscription; it unlocks the API surface for your workspace.
- Your workspace needs a positive credit balance before a send will go out. See Billing API if you are topping up programmatically.
Creating a verification (POST /v1/verify) never bills, whether it is sent, fails to dispatch, or is never checked. Only a successful POST /v1/verify/check bills, exactly once per code.
2. Create a Verify Service (or use the default)
A Verify Service groups settings for one use case, brand, or app: which channels are enabled, code length and TTL, country allowlist, per-minute and per-day send caps, SMS template variant, and custom email subject and body. The friendlyNameyou set is what shows up in the message the user receives, for example the default email subject line "482913 is your Acme verification code" or the default SMS body "Your Acme verification code is 482913."
Every workspace needs at least one Verify Service before it can send: POST /v1/verify without a serviceId resolves to whichever service is flagged as the workspace default, and a workspace with none returns 400 no_default_service. The first Verify Service you create automatically becomes the default, so most teams create exactly one and never think about it again. Fields you omit fall back to sane defaults: 6-digit codes, a 10-minute TTL, US and CA allowed, 10 sends per minute, 1,000 sends per day. Create additional named services only once you have more than one app or brand to verify.
curl -X POST https://api.message.com/v1/verify/services \
-H 'Authorization: Bearer mk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"friendlyName": "Acme"
}'{
"service": {
"id": "vs_7hK2pQmN4rT8sX1c",
"friendlyName": "Acme",
"isDefault": true,
"enabledChannels": ["email", "sms"],
"codeLength": 6,
"codeTtlSeconds": 600,
"allowedCountries": ["US", "CA"],
"perMinuteCap": 10,
"perDayCap": 1000,
"smsTemplateVariant": "standard",
"emailSubject": null,
"emailBodyHtml": null,
"notes": null,
"createdAt": "2026-07-25T18:04:11.000Z",
"updatedAt": "2026-07-25T18:04:11.000Z"
}
}The full field list, defaults, and the PATCH/DELETE surface for services are in the Verify Services API reference.
3. Mint an API key
Verify calls use the same Bearer API key system as the rest of the platform (mk_live_... in production, mk_test_... in a test workspace). Create or reuse a key with the verify scope from Settings → API keys. Keys are workspace-scoped: a key from one workspace can never read or bill another workspace's verifications. See Authentication for the full key and scope model.
4. Send your first verification
POST /v1/verify with a destination and a channel. The to field takes an email address for the email channel or an E.164-ish phone number for sms. Leave serviceId off to use your default service.
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 start = 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 start.json();
console.log(verification);
// {
// id: '1f6a2e4c-...',
// status: 'pending',
// channel: 'email',
// hint: 'a•••@acme.com',
// dispatch: 'sent',
// expiresAt: '2026-07-25T18:14:11.000Z'
// }import requests
start = requests.post(
"https://api.message.com/v1/verify",
headers={"Authorization": "Bearer mk_live_..."},
json={"to": "[email protected]", "channel": "email"},
)
verification = start.json()
print(verification)
# {
# "id": "1f6a2e4c-...",
# "status": "pending",
# "channel": "email",
# "hint": "a•••@acme.com",
# "dispatch": "sent",
# "expiresAt": "2026-07-25T18:14:11.000Z"
# }Reading the response
{
"id": "1f6a2e4c-9d3b-4a1e-8f2c-6b7a9e0d5c3f",
"status": "pending",
"channel": "email",
"hint": "a•••@acme.com",
"dispatch": "sent",
"expiresAt": "2026-07-25T18:14:11.000Z"
}id: the verification's identifier. Store it (session, signed cookie, or your own DB) so you can pass it to the check call.status: alwayspendingimmediately after creation.hint: a masked version of the destination, safe to render in your UI ("we sent a code to a•••@acme.com").dispatch: what actually happened on the wire right now.sentmeans the message left our systems.pending_provisioningmeans the channel is not wired to a live carrier for your workspace yet (see setup times).errormeans a real send failure, for example a carrier rejection.expiresAt: when the code stops being acceptable. Ten minutes by default, configurable per service.
The code itself is never included in the API response, in logs, or in webhook payloads. It only exists in the message the user receives and a salted hash on our side.
5. Check the code
Once the user types the code into your UI, send it back with the verification id from step 4.
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();
console.log(result);
// 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()
print(result)
# success: {"id": "1f6a2e4c-...", "verified": True}
# failure: {"id": "1f6a2e4c-...", "verified": False, "reason": "bad_code"}A match returns 200 with verified: true and bills $0.03 to your workspace. A mismatch returns 200 with verified: false and a reason: the user can retry up to five attempts before the code is locked out. An unknown id returns 404; an id that has already been consumed returns 200 with reason: "already_used". The full error table, including rate limits and country blocks from the start call, is in the dedicated Verify API error codes reference.
Next steps
- Verify channel setup times: which channels are live today and how long the rest take to activate.
- Verify API: multi-channel user verification: the full landing page, channel grid, and product overview.
- Authentication: API key scopes and rotation.
- Webhooks overview: subscribe to
verify.started,verify.succeeded, andverify.failedinstead of polling.