eSMS Verify is a managed OTP API: you call POST /v1/verify/start to text a one-time code to a phone number and POST /v1/verify/check to confirm the code the user typed. eSMS Africa generates the code, sends it, stores it hashed, expires it, limits attempts and resends, so you never store or compare codes yourself.

Last reviewed: 29 October 2026

How to send and check codes with the OTP API

  1. Create a Verify app

    Open Verify (OTP) under DEVELOPERS and click New app. Give it an App name and, optionally, a Sender ID / brand of up to 11 characters, then click Create app. On the app's page you can set the Code length (4 to 8 digits), Expiry (60 to 900 seconds), Max attempts (1 to 10), the Message template and the Allowed countries. Copy the app id, which starts with vera_.

    eSMS Verify page before any app exists, with the New app button numbered 1
    1 New app.

    The template must contain {code}; you can also use {app} (the app name) and {min} (minutes until expiry). A Verify app is optional: you can pass code_length, expiry_seconds, sender_id and template on each call instead.

  2. Start a verification

    Send the number in international format and your app id. Add an Idempotency-Key header so a retried request returns the original verification instead of texting a second code.

    curl -X POST https://sms.esmsafrica.io/v1/verify/start \
      -H "Authorization: Bearer esms_test_your_key_here" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: signup-7f3a-2026" \
      -d '{"to":"+254712345678","app_id":"vera_your_app_id"}'

    The response (HTTP 201) has the verification_id to keep for the check, a masked to, and the expiry:

    {
      "verification_id": "ver_9f8c1234ab",
      "app_id": "vera_your_app_id",
      "to": "+2547****5678",
      "status": "pending",
      "attempts_remaining": 5,
      "sends": 1,
      "expires_at": "2026-10-29T09:05:00+00:00"
    }
  3. Check the code

    curl -X POST https://sms.esmsafrica.io/v1/verify/check \
      -H "Authorization: Bearer esms_test_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{"verification_id":"ver_9f8c1234ab","code":"482193"}'

    The status tells you what to do next:

    statusMeaning
    approvedThe code matched. The number is verified.
    pendingWrong code; attempts_remaining says how many tries are left.
    failedToo many wrong attempts (reason: too_many_attempts). Start a new verification.
    expiredThe code ran out of time before a correct entry.
    canceledYou cancelled the verification.

    You can check by to instead of verification_id; that checks the most recent pending verification for the number.

  4. Resend or cancel

    POST /v1/verify/{verification_id}/resend sends a fresh code and resets the expiry and attempt count, up to 4 sends in total (the first plus 3 resends). POST /v1/verify/{verification_id}/cancel voids a verification the user abandoned. GET /v1/verify/{verification_id} reads the state without using up an attempt.

  5. Use an SDK

    All four SDKs wrap the same calls. In Node.js:

    import { Esms } from "esms-sms";
    const esms = new Esms({ apiKey: process.env.ESMS_API_KEY });
    
    // 1. Send the code
    const v = await esms.verify.start({ to: "+254712345678", appId: process.env.VERIFY_APP_ID });
    
    // 2. Later, check what the user typed
    const result = await esms.verify.check({ verificationId: v.verification_id, code: userInput });
    if (result.status === "approved") {
      // the number is verified
    }

    Python uses esms.verify.start(to, app_id=...) and esms.verify.check(code, verification_id=...); Go uses client.Verify.Start and client.Verify.Check.

  6. Test without sending

    With a test key no SMS is sent and nothing is charged. The start response includes sandbox_code, and 000000 always passes the check. The Try it card on your Verify app's page also lets you run a start and check from the dashboard.

Common errors and how to fix them

What you seeWhat it meansHow to fix it
403 country_not_allowedThe number's country is not in the app's Allowed countries.Add the country to the app, or leave the list empty to allow all countries.
403 app_disabledThe Verify app is switched off.Enable the app on its page in the dashboard.
404 app_not_foundThe app_id does not exist on this account.Copy the id again from the Verify page; it starts with vera_.
400 template_missing_codeYour template has no {code} placeholder.Add {code} to the template (480 characters at most).
403 sender_not_approvedThe sender ID is not approved for that country.Remove it to use the default sender, or get it approved on the Sender IDs page.
403 country_not_enabledLive sending to that country is off for your account.Enable the country on the Countries page.
429 too_many_requestsMore than 5 starts for this number in the last hour.Ask the user to wait, and reuse the existing verification with resend.
429 resend_limit or 409 not_pendingFour sends already, or the verification is no longer pending.Start a new verification.
502 otp_send_failedThe code could not be sent. You were not charged.Retry after a moment; check the number if it keeps failing.

Frequently asked questions

Do I have to store the OTP code?

No. eSMS stores it hashed and checks it for you; you only keep the verification_id.

How long is a code valid?

Five minutes by default. Set 60 to 900 seconds with the app's Expiry or expiry_seconds.

How many wrong attempts are allowed?

Five by default, configurable from 1 to 10 per app. A resend gives the user a fresh set of attempts.

Can I send the code by voice or WhatsApp?

No. The only channel today is SMS.

What happens if the user opted out?

The start is refused with 403 recipient_opted_out and nothing is sent.

Ready? Create a free eSMS account, open Verify (OTP) and create your first app, then test it with a test API key and the code 000000.