# Verify (https://coss.com/products/verify)

> 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.

## How it works

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

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
declare const userIp: string;
declare const typedCode: string;
// ---cut---
// 1. The user typed their number: send them a code.
const { data: verification, error } = await coss.verify.start({
  to: "+14155550123", // or an email address
  ip: userIp, // the end user's address, for the fraud guard
  user_id: "user_42",
});
if (error?.code === "verification_blocked") {
  // The fraud guard stopped it: nothing was sent or billed.
}

// 2. They typed the code they got: check it.
const { data: check } = await coss.verify.check({ to: "+14155550123", code: typedCode });
if (check?.valid) {
  // 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](/docs/verify) and the [API reference](/docs/api-reference/verifications/start-verification).

## 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](/docs/verify/sms) and [email codes](/docs/verify/email).

### 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](/docs/verify/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](/docs/webhooks), and every step (started, sent, check failed, approved) is a line in your environment's [logs](/docs/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](/docs/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.

| Product | Rate | |
| --- | --- | --- |
| Verifications | $0.05 per verification | |
| Verification SMS (US) | $0.0142 per segment | |
| Verification SMS (other countries) | $0.0208 to $0.1134 per segment | By country: coss.com/pricing#sms |

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

## Frequently asked 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.
