SMS Verification Testing for QA & CI/CD Pipelines

Almost every modern signup, login, and account-recovery flow includes a phone-verification step, and that step is the one part of an end-to-end test a script cannot fake. You can fill a form, click a button, and assert a redirect, but the moment your test reaches "enter the code we just texted you," it stalls. Teams paper over the gap with a shared SIM on someone's desk, a hard-coded staging bypass, or a test account that skips verification entirely, and every one of those workarounds means the real production path ships untested. This page is about closing that gap: getting a real number and a real one-time code programmatically, inside your test suite, so the full verification flow runs on every build.

Why OTP is the hardest step to automate

A verification SMS is designed to prove a human is holding a specific phone. That assumption is exactly what makes it hard to test: there is no human, and no phone, in a CI runner. So the OTP screen becomes the wall where an otherwise green end-to-end test goes red. The common workarounds each cost something. A shared SIM card on a desk does not run in CI, serialises every test that needs it, and breaks the moment two builds run at once. A staging bypass that skips the code means the exact branch your users hit in production is the one branch you never exercise. A frozen test account drifts out of sync with the real signup logic the day someone changes it.

What a test actually needs is the same thing a real user has: a number that can receive a real code, once, on demand, then be released. Done programmatically, the OTP stops being a manual interruption and becomes one more assertion in the flow. Your test rents a number, points the signup form at it, waits for the code, and verifies the redirect, with no SIM hardware, no human in the loop, and no untested bypass branch. That is what an SMS verification API gives a test suite.

How programmatic SMS verification works

The flow is three calls against the REST API, wrapped around your existing test. Authentication is a single header, so it drops cleanly into a CI secret:

  1. Rent a number: POST to /api/v1/activations with the service and country you are testing. You get back an activation id and a real phoneNumber to feed into your signup form.
  2. Run your flow: drive the actual signup or login UI (or its API) with that number, exactly as a user would, so the real verification branch executes.
  3. Read the code: poll GET /api/v1/activations/:id until status is RECEIVED, then read smsCode. Submit it, call /complete to close out, or DELETE to cancel and get an automatic refund if nothing arrived.

Drop it into your test suite

Both samples below call the live REST API. Authentication is the x-api-key header; the body takes a serviceCode and a countryIso (an optional provider, "1" or "2", picks the number pool). The smsCode field stays null until status is RECEIVED, which is why the loop polls.

Node.js + Playwright

const API = 'https://smsbulk.net/api/v1';
const headers = {
  'x-api-key': process.env.SMSBULK_API_KEY,
  'Content-Type': 'application/json',
};

// 1. Rent a real number for the service + country you are testing.
const rent = await fetch(`${API}/activations`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ serviceCode: 'wa', countryIso: 'TR' }),
}).then((r) => r.json());
const { id, phoneNumber } = rent;

// 2. Drive your real signup flow with that number (Playwright).
await page.fill('#phone', phoneNumber);
await page.click('#send-code');

// 3. Poll until the code lands. smsCode stays null until status === 'RECEIVED'.
let code = null;
for (let i = 0; i < 30 && !code; i++) {
  const a = await fetch(`${API}/activations/${id}`, { headers })
    .then((r) => r.json());
  if (a.status === 'RECEIVED') code = a.smsCode;
  else await new Promise((r) => setTimeout(r, 3000));
}

// 4. Submit the OTP, then close out the activation.
await page.fill('#otp', code);
await page.click('#verify');
await fetch(`${API}/activations/${id}/complete`, { method: 'POST', headers });

Python + pytest + Selenium

import os, time, requests

API = "https://smsbulk.net/api/v1"
HEADERS = {"x-api-key": os.environ["SMSBULK_API_KEY"]}

def test_signup_with_real_otp(driver):
    # 1. Rent a real number for the service + country under test.
    rent = requests.post(
        f"{API}/activations",
        headers=HEADERS,
        json={"serviceCode": "wa", "countryIso": "TR"},
    ).json()
    activation_id, phone = rent["id"], rent["phoneNumber"]

    # 2. Drive the real signup flow (Selenium).
    driver.find_element("id", "phone").send_keys(phone)
    driver.find_element("id", "send-code").click()

    # 3. Poll until status == RECEIVED; smsCode is null before that.
    code = None
    for _ in range(30):
        a = requests.get(
            f"{API}/activations/{activation_id}", headers=HEADERS
        ).json()
        if a["status"] == "RECEIVED":
            code = a["smsCode"]
            break
        time.sleep(3)
    assert code, "OTP did not arrive in time"

    # 4. Submit the OTP and close out the activation.
    driver.find_element("id", "otp").send_keys(code)
    requests.post(
        f"{API}/activations/{activation_id}/complete", headers=HEADERS
    )

Swap countryIso to test a different region, or serviceCode to test a different service. The same two endpoints back any framework: Cypress, Puppeteer, WebdriverIO, or a plain HTTP client in a CI step. Full request and response schemas are in the API docs.

Wire a real OTP into your next CI run.

What teams use it for

  • End-to-end signup regression: register a clean account on every run, receive the OTP, and assert the full path through verification instead of stubbing it out.
  • CI/CD verification gates: block a release until the live login or onboarding flow proves it still works against a real number and a real code.
  • Multi-region coverage: change countryIso to confirm the flow works for the regions you ship to, across broad international coverage. See services and pricing.
  • Parallel test isolation: every test rents its own number, so suites run concurrently without a shared SIM serialising them or numbers colliding between builds.
  • Smoke-testing the OTP path: a scheduled job that orders a number and checks a code still arrives end to end, catching a broken provider or template before users do.

Why SMSBulk fits test automation

A clean REST API with x-api-key auth slots straight into a CI secret, with no browser SDK or device farm to maintain. You pay per number rather than buying SIM hardware or signing a carrier contract, so a test that runs ten thousand times a month costs only the numbers it actually used. Broad international coverage lets one suite exercise the regions you ship to by changing a single field, and a DELETE on an activation refunds the wallet automatically when no code arrived, so flaky-network and out-of-stock test runs do not quietly burn budget. A built-in spend ceiling keeps a runaway loop in CI from draining the balance.

If you would rather an autonomous agent drive the whole flow itself, rather than your own test code calling the REST API, the same platform exposes a Model Context Protocol server. That path is covered separately in our receive SMS for AI agents guide. And if you just need to receive a code by hand or from a quick script, see the receive SMS online guide. The full endpoint reference for everything on this page lives in the API docs.

Honest and responsible use

This is a tool for testing flows you own or are explicitly authorised to test: your own product, a client engagement you are contracted for, or a staging environment you control. That is the entire use case, and it is a legitimate, everyday part of shipping software.

It is not for mass-registering accounts on third-party services, evading another company's rate limits, or getting around a verification step you were never meant to clear. SMS verification testing means proving your own verification works; it does not mean defeating someone else's. Numbers are tied to your account and activity is logged, so treat this the way you would any credential in your pipeline.

Honest positioning

Programmatic SMS for testing is not a one-vendor space, and we will not pretend otherwise. Several providers expose a verification API that a test suite can call, and the right choice depends on the regions you need, the services you verify against, your per-number cost, and how clean the API is to wire into CI. Compare on the axes that matter to your pipeline. Our best SMS verification APIs guide lays out the broader field honestly, including where other providers are the better fit.

Frequently asked questions

What is SMS verification testing?

It is testing the phone-verification step of your own signup, login, or recovery flow end to end, by renting a real number through an API, running your flow against it, and reading the real OTP, instead of stubbing the step out.

Can I automate OTP in Playwright, Cypress, or Selenium?

Yes. Any framework that can make an HTTP request can call the REST API: rent a number, drive your UI, then poll for the code and submit it. The Node and Python samples above show the full loop, and the pattern is identical in Cypress, Puppeteer, or WebdriverIO.

How do I get the SMS code in my test?

Poll GET /api/v1/activations/:id until status is RECEIVED, then read the smsCode field. It is null before the code arrives, so loop with a short delay and a timeout. See the API docs for the full response schema.

Which countries and services can I test against?

Coverage is broad and international across many services and regions; you choose with serviceCode and countryIso on each request. Browse the live services list, and check pricing for per-number cost.

Is this the same as bypassing 2FA?

No. This tests verification flows you own or are authorised to test, proving your own OTP path works. It is not a way to defeat someone else's verification or mass-create third-party accounts. Testing your verification and bypassing another company's are different things.

How is this different from using an AI agent with MCP?

Here your test code calls the REST API directly and stays in control of the loop. With MCP, an autonomous agent discovers the tools and drives the flow itself as part of its own reasoning. Same platform, different driver; that approach is covered in our receive SMS for AI agents guide.

Test the verification step you cannot fake