One-time codes, without the toll fraud.

coss.com Verify is a one-time code API that proves someone has a phone number or mailbox, with a fraud guard that stops SMS pumping before you pay.

Read the docs

How it works

Your backend makes two calls. coss.com makes the code, sends it, counts the attempts and expires it.

// 1. The user typed their number: send them a code.
const { : ,  } = await ..({
  : "+14155550123", // or an email address
  : , // the end user's address, for the fraud guard
  : "user_42",
});
if (?. === "verification_blocked") {
  // The fraud guard stopped it: nothing was sent or billed.
}

// 2. They typed the code they got: check it.
const { :  } = await ..({ : "+14155550123", :  });
if (?.) {
  // The number is theirs.
}

A wrong code spends an attempt, and the last one fails the verification. Checking an approved verification again with its code stays valid until it expires, and is billed once. See the Verify guide and the API reference.

Codes without the plumbing

Everything between asking for a code and knowing it is them, behind one key.

SMS or email

A number in E.164 form gets an SMS, an email address an email. SMS go from coss.com's shared numbers or one of your own registered numbers; email from your verified domain or from coss.com's sender. See SMS codes and email codes.

Never stored, never logged

coss.com keeps only an HMAC of each code, until the verification expires, and erases the message text once it is sent. Codes never appear in logs, webhooks or email subjects.

Resends that just work

Starting again for a pending destination sends a new code, retires the old one and restarts the expiry, up to 5 codes per verification. No resend endpoint, no state on your side.

A fraud guard that runs first

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

Allowed countries

SMS codes go only to the countries in the environment's Verify settings, the US and Canada by default. Anything else fails with country_not_allowed before a message is sent.

Webhooks and logs

verification.approved, verification.failed and verification.blocked reach your webhooks, and every step (started, sent, check failed, approved) is a line in your environment's logs.

Stops SMS pumping before you pay for it

Pumping is codes requested for numbers whose owners earn money from every text. Nobody ever enters them.

The fraud guard watches for exactly that. It blocks premium-rate and shared-cost numbers, and numbers that can't receive a text. It counts verifications per number prefix (the number without its last four digits) and per country, and compares them with how many were approved: real people enter their codes, pumping never does. A burst to one prefix, or a prefix or country whose codes are rarely entered, is blocked with a reason such as verify.prefix_low_conversion.

It uses Risk's building blocks and your environment's Risk lists, where an allow-list entry wins. It never blocks on a signal it couldn't read. Each destination also gets at most 5 codes every 10 minutes, so a looping form stops on its own.

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
Stored
an HMAC of the code, until it expires
Verifications kept
30 days
Sandbox codes
always zeros

Pricing

You pay per approved verification, plus the channel. Blocked, failed and expired ones cost only the messages actually sent.

An SMS from coss.com's shared numbers is billed at its country's SMS rate, one from your own number like any SMS, and email codes cost nothing extra. Sandbox verifications are never billed. See pricing for the plans.

Questions

How do I add phone verification with coss.com?
Call coss.verify.start() with the phone number and the end 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; only valid true from the check proves the number is theirs.
Can Verify send codes by email?
Yes. Start a verification with an email address instead of a phone number and the code goes by email, from an address on one of your verified domains or from coss.com's sender as "your app via coss.com". The code is never in the subject, and the email has no links or tracking.
How does coss.com stop SMS pumping?
Every new verification passes a fraud guard 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 stored with its reasons, and nothing is sent or billed.
Does coss.com store the verification code?
No. Only an HMAC of the code is kept, until the verification expires, and the text of the SMS or email is erased once it is sent. The code never appears in logs or webhooks.
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.
How is Verify priced?
You pay per approved verification, plus the channel. An SMS from coss.com's shared numbers is billed at its country's SMS rate, one from your own number like any SMS, and email codes cost nothing extra. Blocked, failed and expired verifications cost only the messages actually sent. The pricing page lists the rates.
How do I test Verify without sending real codes?
Use a sandbox key. The code is always zeros, such as 000000 with the default length, SMS go to the SMS simulator and emails to the sandbox's fake provider, and nothing is billed. The fraud guard still runs, so a premium-rate test number is blocked as in live environments.
On the same key, SDK, webhooks and logs: nothing more to set up.

Compared

How it stacks up against the services you may be using, with sources.

Start with Verify today.

Development keys record every request and event and deliver nothing, so you can build and test before you verify a domain. Hand your coding agent this prompt:

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