Handling verdicts
What to do with allow, challenge, and block — and how to wire the assessment ID into your user record.
POST /v1/assess always returns one of three verdicts. Your code must
handle all three. The verdict is a strong signal, not a guarantee — pair
it with your own checks at high-risk steps.
The three verdicts
| Verdict | What you do |
|---|---|
allow | Create the account. No friction added. |
challenge | Add friction (email verification, CAPTCHA, manual review). |
block | Refuse the signup. Show a generic error. |
Never assume the API only returns allow. A new threshold config or a
sudden disposable-domain blocklist update can flip a previously-allowed
flow into challenge or block overnight.
allow
Proceed with account creation. We still recommend storing the
request_id on the user record so you can investigate later if the
account turns bad.
const { request_id, verdict } = await assess({ email, ip });
if (verdict === "allow") {
await db.users.insert({ email, ip, shieldsignup_request_id: request_id });
}challenge
The signup looks suspicious but isn't definitively bad. Pick one of the following — don't stack them:
- Send an email verification code before creating the account.
- Show a CAPTCHA.
- Add the account to a manual-review queue.
Whichever path you pick, store the request_id on the pending user
record so you can correlate the verification result with the assessment
later.
if (verdict === "challenge") {
await db.pending_signups.insert({
email,
ip,
shieldsignup_request_id: request_id,
status: "awaiting_verification",
});
await sendVerificationEmail(email);
return { challenge: true };
}block
Do not create the account. Show a generic error message. Don't tell the user why — specificity helps bad actors adapt.
Recommended copy:
"We weren't able to create an account with that email address."
Don't say:
"Your email was flagged as disposable."
if (verdict === "block") {
return {
error: "We weren't able to create an account with that email address.",
};
}If you want to give the legitimate-but-blocked user a path forward, link
to a contact form. Include the request_id in the form so support can
look it up via GET /v1/assess/:request_id.
Storing the request_id
Persist the request_id on the user (or pending-user) record. It costs
nothing and saves real time during incidents.
alter table users
add column shieldsignup_request_id text;
create index users_shieldsignup_request_id_idx
on users (shieldsignup_request_id);With the ID stored, you can pull up the full assessment from the
ShieldSignup dashboard or via GET /v1/assess/:request_id.
You can also report a wrong call back via
POST /v1/feedback — that improves
detection for everyone.
Never trust only the verdict
The verdict is one signal in your overall fraud posture. Combine it with:
- Email verification at signup for any plan that costs you money.
- Rate-limiting account creation per IP and per email domain in your own application layer (don't rely solely on ShieldSignup velocity signals).
- Phone or payment-method verification for high-value actions (paid plan upgrades, payouts, referral redemptions).
A challenge should never automatically auto-promote to allow after
a successful CAPTCHA without you also doing email verification — many
fraud rings now solve CAPTCHAs at scale.