ShieldSignup
Guides

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

VerdictWhat you do
allowCreate the account. No friction added.
challengeAdd friction (email verification, CAPTCHA, manual review).
blockRefuse 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.

On this page