# What to do after a signup check comes back dirty

_2026-09-22 · Boundstone (https://boundstone.io/blog/graduated-signup-policy)_


Adding a signup check is a day of work. Deciding what happens when it fires is the part that quietly becomes a product, and it is usually skipped.

The default is a hard block, because it is one line of code. A month later the support queue has real customers in it, nobody can tell which blocks were correct, and the check gets switched off. That is not a detection failure. It is a missing ladder.

## The four rungs

**Allow.** The clean majority. Nothing fires, nothing is logged beyond the verdict, the user never knows a check happened. This should be the overwhelming default, and if it is not, your thresholds are wrong rather than your users being suspicious.

**Limit.** The account is created but constrained — a shorter trial, a lower rate limit, no outbound sending, no invite ability. This rung is the one most teams do not build, and it is the most valuable. It converts an irreversible decision into a reversible one, and a real customer usually never notices.

**Review.** A human looks. Only worth building if someone is genuinely going to look, and only for volumes where they can. A review queue nobody empties is a block with extra steps and worse feelings.

**Block.** Reserved for combinations you could explain to the customer without embarrassment. If you cannot articulate why this specific pattern is not a real person, it is a limit, not a block.

## One signal should almost never reach the bottom rung

A disposable address is what a privacy-conscious real person uses when they do not trust you yet — which, at signup, is every person. A VoIP number is how plenty of people have a phone. A `reserved` source IP is odd, but odd is not fraudulent.

Combinations are different in kind. A brand-new throwaway inbox, a VoIP number and a source address that classifies as `reserved`, all in one submission, is a pattern that is hard to explain innocently. That is the shape worth a block.

The practical rule: **count independent signals, and let the count pick the rung.** One is a limit at most, two is a review, three is a block. Tune the boundaries against your own data, but keep the shape.

## Write down who eats the false positive

Every rung except allow has a false-positive cost, and it lands on somebody. Decide who before you ship:

- If the customer eats it, the error message must contain a route back — a support address, not a generic rejection. A blocked real customer who does not know why never returns and never tells you.
- If support eats it, they need the signal that triggered the action, in the record. "Flagged" is not enough to act on.
- If nobody eats it, you are running an unmeasurable system and will eventually turn it off during an incident, permanently.

Log the triggering signals with the outcome. Without that, you cannot compute a false-positive rate later, which means you cannot tune the ladder — you can only keep it or kill it.

## Only collect what the ladder consumes

`POST /v1/verify/email` gives syntax, a live MX lookup, disposable and role flags. `POST /v1/verify/phone` gives validity, `line_type` and E.164. `POST /v1/verify/ip` gives format, version and range classification.

Every response also carries `checks.not_performed`, and on the IP side that list is long and load-bearing: `geolocation`, `asn`, `hosting_datacenter`, `proxy_vpn_tor` and `reputation` all come back not-run, because they need licensed data. If your ladder has a rung that depends on knowing someone is on a VPN, Boundstone cannot fill it and will not pretend to — you need a fraud platform for that rung.

The corollary is the useful part: do not collect a signal your policy has no rule for. An unused field is a liability at rest.

## The short version

- **Detection is the easy half.** The ladder between "check fired" and "what happens" is the actual product decision.
- **Four rungs: allow, limit, review, block.** Most teams build the first and last and skip the two that matter.
- **Limit is the underbuilt rung.** It turns an irreversible decision into a reversible one the real customer never notices.
- **One signal is a limit, not a block.** Disposable addresses and VoIP numbers both have common innocent explanations.
- **Let the count of independent signals pick the rung**, and tune the boundaries on your own data.
- **Name who eats the false positive** before you ship, and give a blocked real person a route back.
- **Log the triggering signal with the outcome**, or the system can only be kept or killed, never tuned.
- **Do not collect signals your ladder has no rule for** — and note that VPN, proxy, ASN and reputation are `not_performed` here.
