message.comDevelopers

Verify push quickstart Disabled

Push verification delivers a challenge straight to your own mobile app through a push notification, so the user approves a login or confirms a code without leaving your app or switching to their SMS inbox. It is the lowest friction option for users who already have your app installed. The committed check contract below is stable; the disabled-for-sending state is called out where it applies.

Setup time: 1 day$0.03 per successful verification

Push is currently disabled. POST /v1/verify with channel: "push" returns 422 with { "error": "channel_not_available", "channel": "push" }. The verification check contract shown below is the same committed, channel-agnostic call every channel uses.

What push verification is

Instead of texting or emailing a code, push sends a notification to your app on the user's registered device. Delivery runs over Apple Push Notification service (APNs) and Firebase Cloud Messaging (FCM), which is why push needs your app's own push credentials registered against your Verify Service. The user sees the challenge in your app and either approves it or reads a code out of the notification, and your backend confirms the result with the standard check call.

When to choose push

Choose push when the user already has your mobile app installed and you want the smoothest possible re-auth or step-up flow, with no channel switch and no carrier cost per message. It is a poor fit for first-time signup before the app exists on the device, or for web-only products. For those, send a code with SMS or email; consider TOTP for an app-based second factor that needs no network delivery at all.

Prerequisites

  • A message.com workspace with Verify activated. See Get started with Verify.
  • A Verify Service with push in its enabledChannels.
  • An API key with the verify scope (mk_live_...). See Authentication.
  • Your app's push credentials (APNs and/or FCM) registered against your Verify Service. Registering those credentials is the 1-day setup step. See setup times.

The verification contract

The committed and stable piece of the Verify API for push today is the check call: once the user acts on the notification, your backend submits the result against the verification id and gets back { id, verified }. This is the exact channel-agnostic check every channel uses.

cURL (macOS/Linux)
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 (Windows)
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\"}"
Node
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' }
Python
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 returns 200 with verified: false and a reason. Read the verified field, not the HTTP status.

The push enrollment surface (registering a device token, associating it with a user, and managing a user's devices) is not part of the committed API yet, so this page documents only the generic start and check contract, not push-specific device-registration endpoints. The start call today is the same POST /v1/verify shape as every channel and returns 422 channel_not_available for push.

Starting a push verification today

The generic start call accepts channel: "push" and returns the disabled response.

cURL (macOS/Linux)
curl -X POST https://api.message.com/v1/verify \
  -H 'Authorization: Bearer mk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
    "to": "user_8123",
    "channel": "push"
  }'
422 Unprocessable (today)
{
  "error": "channel_not_available",
  "channel": "push"
}

Fraud interactions

Push targets a device the user has already enrolled with your app, so it is not exposed to the phone-number abuse (SMS pumping, oracle probing) that the delivered phone channels face. The controls that remain apply on the check side and to volume:

  • Attempt cap. Five wrong codes lock the verification with too_many_attempts, the same as every channel.
  • Per-service velocity. The per-minute and per-day caps still bound how many push challenges a service can issue, returning 429 rate_limited with scope: "workspace".
  • Enrollment binding. Because a challenge only goes to a device already tied to the user, an attacker cannot redirect it to a device they control the way they could spoof a phone number.

Velocity caps and the rest of the Verify fraud model are covered in depth in the Verify fraud guide.

Troubleshooting

SymptomCause and fix
422 channel_not_availableExpected today: push is disabled. Use SMS or email now. Do not retry this call for the same channel; it will not become available mid-session.
400 channel_not_enabledThe resolved service does not list push in enabledChannels. This is a per-service setting, distinct from the platform-wide channel_not_available.
Check returns reason: "too_many_attempts"Five wrong codes locked the verification. Start a fresh verification rather than continuing to retry.
Check returns reason: "expired"The challenge 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.