Verify Users with OTP

Use a one-time password when you need to confirm that a user controls an international phone number. Commerce sends the token by SMS and records each verification attempt; your application must inspect the attempt verdict before granting access.

Prerequisites

  • An opaque Commerce API key stored in COMMERCE_API_KEY
  • The user's phone number in international format
  • A server-side place to associate the typed OTP transaction ID with the pending action

Initiate the SMS verification

Call Initiate OTP transaction with recipient, service_name, and token_size. The current API requires a token size from 5 through 10. The service name must be 2-20 characters; an optional sender must be 3-12 characters. Validity defaults to 10 minutes and can be set from 3 through 10,080 minutes.

Persist the returned ID in the form ot_<TRANSACTION_ID>. A transaction can be pending_delivery before transmission details are available and pending_verification after the SMS is sent.

Verify the submitted token

Submit the transaction ID, the same international phone number, and the token entered by the user. An HTTP 200 response means the attempt was recorded, not that the token matched. Continue only when verification_attempt.result.verdict is pass.

RECIPIENT='<USER_PHONE_E164>'

init=$(curl https://api.zebo.dev/otp/initiate \
  -H "Authorization: Bearer $COMMERCE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: verify-user-123" \
  -d "{
    \"recipient\": \"$RECIPIENT\",
\"sender\": \"MyApp\",
\"service_name\": \"MyApp\",
\"token_alphabet_type\": \"numeric\",
\"token_size\": 6
}")

transaction_id=$(jq -r '.transaction.id' <<< "$init")
read -r -s OTP_CODE
verify=$(jq -n \
  --arg recipient "$RECIPIENT" \
 --arg token "$OTP_CODE" \
  --arg transaction_id "$transaction_id" \
 '{recipient: $recipient, token: $token, transaction_id: $transaction_id}' | \
 curl https://api.zebo.dev/otp/verify \
 -H "Authorization: Bearer $COMMERCE_API_KEY" \
 -H "Content-Type: application/json" \
 --data-binary @-)

jq -e '.verification_attempt.result.verdict == "pass"' <<< "$verify"

Do not log the submitted token or verification response body. If the verdict is fail, keep the protected action pending and let the user retry only within your attempt policy. Use Lookup OTP transaction to refresh the transaction state, or Cancel OTP transaction when the flow is abandoned.

Was this page helpful?