Verify phone numbers with one-time codes.

Verify phone numbers with one-time SMS codes from coss.com's shared numbers or your own, a fraud guard that stops SMS pumping, and Risk line checks.

Read the docs

The problem

Texting a code is the easy part. The hard parts are getting a sender past carriers, storing nothing worth stealing, and not paying for fraud.

Phone verification proves someone has a number, at sign-up or before a sensitive action. It also attracts SMS pumping: scripts request codes for numbers whose owners earn money from every text, and you pay for messages nobody reads. Building it yourself means generating and hashing codes, counting attempts, handling resends and expiry, and working out who's abusing your form.

coss.com covers this with three products on one key: Verify for the codes and the fraud guard, SMS if you'd rather send from your own number, and Risk for lists of who to allow or block and for checking the line itself.

How it works

Your backend makes two calls. Risk's phone check is an optional step before them.

// Optional: look at the line before spending a code on it.
const { :  } = await ..(.);
if (?. === "non_fixed_voip") throw new ("Please use a mobile number.");

// 1. Send a code. The fraud guard runs first.
const {  } = await ..({
  : .,
  : ., // the end user's address, not your server's
  : "user_42",
});
if (?. === "verification_blocked") throw new ("We can't send a code right now.");

// 2. Check the code they typed, on your backend.
const { :  } = await ..({ : ., : . });
if (?.) {
  // The number is theirs.
}

Read more in the fraud guard, phone checks and the API reference.

Sending live codes

Codes go from coss.com's shared numbers by default, so there's no number to buy and nothing to register first.

  1. Choose a paid plan. Live SMS is available on paid plans.
  2. Start sending. coss.com registered its shared numbers with carriers for one-time codes, and each destination always gets its codes from the same number. The messages are billed to you by country once a carrier takes them. See SMS codes.
  3. Use your own number if you'd rather. Choose one in Verify's settings when your users should see your number, or for countries the shared numbers don't reach. It must be registered and approved, and its messages appear under SMS in your environment and are billed as SMS. See numbers and registration.

You can see which of your own numbers are ready to send:

const { :  } = await ...();
const  = ?..(() => .);

You can also build against a sandbox environment, where messages are simulated and the code is always zeros. See testing Verify.

await ..({ : "+15005550006" });
const {  } = await ..({ : "+15005550006", : "000000" });

What you get

Everything between asking for a code and knowing it's them, with the abuse checks in front of it.

Two calls, no code handling

coss.verify.start() sends a code and coss.verify.check() compares what the user typed. Attempts, expiry and resends are handled for you. Starting again for a pending number sends a new code and retires the old one.

Nothing worth stealing

coss.com keeps only a keyed hash of each code, until the verification expires, and erases the message text once it's sent. Codes never appear in logs or webhooks.

A fraud guard that runs first

Each new verification is checked before a code goes out: premium-rate and non-mobile numbers, Tor exit nodes, and velocity per IP, user, number prefix and country. A blocked one is stored with its reasons and costs nothing. See the fraud guard.

Your Risk lists apply

Allow-list entries for a user, IP address or country let a verification through, and block-list entries stop it, using the same lists as coss.risk.assess().

Line type and carrier

coss.risk.checkPhone() says whether a number exists and whether it's mobile, landline or VoIP, and which network it's on now. See phone checks.

Allowed countries and opt-outs

Codes go only to the countries in Verify's settings, the US and Canada by default. Anything else fails with country_not_allowed. Someone who replied STOP to a number can't get codes from it until they reply START.

Webhooks and logs

verification.approved, verification.failed and verification.blocked reach your webhooks, and each step is a line in your environment's logs.

Email as a second channel

Pass an email address instead of a number and the code goes by email, for users who can't get a text. The calls are the same.

Specifications

The defaults of an environment without settings, and what each setting allows.

Code length
6 digits by default, 4 to 10
Code lifetime
10 minutes by default, 1 to 30 minutes
Wrong codes allowed
5 by default, 1 to 10
Codes per verification
5, the first and its resends
Codes per destination
5 every 10 minutes
Default SMS countries
US and Canada
Live SMS sender
coss.com's shared numbers, or your own registered number
Phone checks per organization
5 per second, bursts of 10, 1,000 per day
Verifications kept
30 days
Sandbox codes
always zeros

Pricing

You pay per approved verification, plus the SMS that carried the code.

A blocked verification costs nothing, and one that fails or expires costs only the messages actually sent. Every plan includes 100 risk phone checks a month. Sandbox verifications are never billed. See pricing for the plans.

Questions

How do I verify a phone number in my app?
Call coss.verify.start() with the number in E.164 form and the user's IP address, then coss.verify.check() with the code they typed. coss.com makes the code, sends it, counts the attempts and expires it, and only a valid check proves the number is theirs.
Do I need my own phone number to send codes?
No. Live codes go from coss.com's shared numbers by default, which coss.com registered with carriers, and their messages are billed to you by country. Choose one of your own registered numbers in Verify's settings when your users should see your number, or for countries the shared numbers don't reach. Sandbox environments need no number.
How long does it take to start sending live codes?
Live SMS needs a paid plan, and the shared numbers need no registration from you. If you send from your own number instead, US 10DLC registration goes through carrier review and usually takes a few days. You can build and test in a sandbox environment at any point.
How does coss.com stop SMS pumping?
Verify's fraud guard checks every new verification before a code is sent. It blocks premium-rate and non-mobile numbers, Tor exit nodes, entries on your Risk block list, bursts from one IP address, user or number prefix, and prefixes or countries whose codes are rarely entered. A blocked verification is neither sent nor billed.
Can I find out whether a number is VoIP before sending a code?
Yes. coss.risk.checkPhone() returns the number's line type and carrier, including non_fixed_voip for numbers anyone can get online in minutes. It is a separate call from Verify, and you decide what to do with the answer.
What happens if a user asks for the code again?
Starting a verification for the same destination while one is pending sends a new code, the earlier one stops working and the expiry restarts. A verification can send at most 5 codes, and each destination gets at most 5 every 10 minutes.
Does coss.com store the code?
No. Only a keyed hash of the code is kept, until the verification expires, and the text of the SMS is erased once it is sent. The code never appears in logs or webhooks.
How do I test verification without sending real texts?
Use a sandbox key. The code is always zeros, messages go to the SMS simulator and appear in the dashboard, and nothing is billed. The fraud guard still runs, so a premium-rate test number is blocked as in live environments.

The products

One key, one SDK, one log for all of them.

Build it this afternoon.

Hand your coding agent this prompt, then pick the parts you need:

Add coss.com to this project: read https://coss.com/llms.txt, get a development key with `npx coss-cli init --agents`, and build and test with it.

Pricing