If your app sends a one-time code by SMS, sooner or later you need to test that flow with a real number: does the message arrive, in what format, how fast, and does it work for users in other countries? Buying local SIM cards for every country you support doesn't scale.

One way to do it is with online one-time numbers. You request a number for a country and an app, trigger the SMS from your own sign-up form, and read the code back over HTTP.

Below is a small Python script that does this with the Receive SMS API. Full disclosure: I run that service, but the same pattern works with any provider that has a buy-number / poll-for-SMS API.

The flow

  1. get_number buys a number for an app and country. Your balance is charged, and refunded automatically if no SMS arrives before the number expires (15 minutes).
  2. Enter the number in the sign-up form you are testing.
  3. Poll get_sms every 5 seconds until the code arrives (it returns HTTP 404 until then).
  4. Call finish when you are done, or reject_code to cancel and get refunded if nothing came.

Code

import time import requests API = "https://receive-smss.net/api/v1" HEADERS = {"Authorization": "Bearer YOUR_API_KEY"} order = requests.get(f"{API}/get_number", params={"app": "whatsapp", "country": "US"}, headers=HEADERS).json() if not order["success"]: raise SystemExit(order["error"]) number_id = order["data"]["id"] print("Use this number:", order["data"]["number"]) # ... trigger the SMS from the app you are testing ... for _ in range(180): # up to 15 minutes sms = requests.get(f"{API}/get_sms", params={"n_id": number_id}, headers=HEADERS).json() if sms["success"]: print("Code:", sms["code"]) requests.get(f"{API}/finish", params={"n_id": number_id}, headers=HEADERS) break time.sleep(5) 

A few useful extras:

  • max_price on get_number makes the call fail instead of paying more than you expect.
  • get_rates?country=US lists apps and prices for a country, so a test suite can pick a country that has stock.
  • HTTP 503 means no numbers are available right now, and you are not charged.
  • The rate limit is 60 requests a minute; a 429 comes with a Retry-After header.

Things to keep in mind

  • Use these numbers for test accounts only. A one-time number is gone after 15 minutes, so never attach it to an account you need to recover later.
  • Keep the API key in an environment variable or your CI secrets, not in the repo. Send it in the Authorization header rather than the query string, because URLs end up in logs.
  • Some apps block virtual numbers. That's useful to know too: if your own app is meant to block them, this is an easy way to test that it does.

The full API reference is at receive-smss.net/api-docs. Happy to answer questions in the comments.