Signup Protection, email verification and Domain Intelligence share one credit balance. See how credits work

Start for free

Blog

How to read a signup check

A signup check combines the email decision with evidence about the visit. Read the overall action, then inspect the two parts to understand what changed it.

Published

On this page

Use the response’s outer action to decide how registration should proceed. A deliverable email address can still require confirmation because of the visit signals. A signal that requires confirmation does not establish that the visitor is a bot.

The examples below describe the combined response from POST /v1/signup-checks. They are synthetic, shortened illustrations of selected fields, not complete API responses or measurements of real visitors.

Read the response in this order

  1. Read the outer action: allow, allow_with_email_confirmation or reject. This recommendation accounts for both parts of the check.
  2. Read email.action and email.status to understand the email decision. The email’s reason_codes explain its evidence and limits.
  3. Read signals.status to see whether visit signals were evaluated, the token was unusable or the service was unavailable.
  4. Inspect signals.findings and signal_codes to see which findings required confirmation. Check not_evaluated and not_evaluated_classes, when present, for comparisons that could not be made.

These fields answer different questions. evaluated describes the state of the signal assessment; it is not a successful signup verdict. An empty signal_codes list means no finding raised the recommendation, not that every comparison ran or the visit was proved safe.

If you use POST /v1/signup-signals instead, the response has no email part. Its action is the signals-only recommendation, as though email had returned allow. It cannot replace the email policy you apply elsewhere.

A deliverable email can still require confirmation

In this example, the email result allows registration. The same token has been presented in a new operation, so a signal asks for confirmation:

{
  "action": "allow_with_email_confirmation",
  "signal_codes": ["fp_token_reused"],
  "email": {"status": "deliverable", "action": "allow"},
  "signals": {
    "status": "evaluated",
    "findings": [
      {"code": "fp_token_reused", "class": "token", "default_raises": true}
    ]
  }
}

Reading only email.action would miss the confirmation requirement. The outer action includes it. The finding records token reuse; it does not establish why the token was used again or who submitted the request.

Your application implements allow_with_email_confirmation: send a confirmation message and check its link or code before granting the access that depends on mailbox control. Emvero’s email verification sends no message to the address being checked. The distinction between email verification and email confirmation matters here: confirmation establishes access to a mailbox, not a unique human identity.

Signals can move an email decision from allow to allow_with_email_confirmation. They cannot cause reject, and they cannot soften an email rejection or remove an existing confirmation requirement. When the outer action is reject, its basis is the email decision.

Find which evidence changed the action

signals.findings contains the reported findings. Each finding’s default_raises says whether it asks for confirmation under Emvero’s default policy. The top-level signal_codes contains the codes of findings with default_raises: true, in their returned order and without duplicates.

A finding can therefore appear without changing the action. This VPN example keeps the email decision:

{
  "action": "allow",
  "signal_codes": [],
  "email": {"status": "deliverable", "action": "allow"},
  "signals": {
    "status": "evaluated",
    "findings": [
      {"code": "fp_network_vpn", "class": "identity", "default_raises": false}
    ]
  }
}

fp_network_vpn reports a network observation. With default_raises: false, that finding alone does not require confirmation. VPN, Tor, proxy and hosting findings are informational by default; a network label does not establish abuse. Read the returned flag rather than guessing from a code’s name. The signal-code reference defines the vocabulary.

The email’s reason_codes stay inside email. They explain email evidence and do not become signal codes. The email-result article covers that part in detail. Keep the two code lists separate when displaying a result or investigating a signup.

signal_codes can be non-empty even when email already requires confirmation or rejection. The list records the signals that raised their part of the recommendation; it does not tell you that signals were the only cause of the final action.

Browser findings and notes have different roles

signals.probe reports whether the browser probe was read and which of its checks could run. fp_probe_forged and fp_probe_automation have default_raises: true; they can require email confirmation. fp_probe_tampering is informational because browser extensions and page code can also alter native properties.

The separate signals.notes array carries rtt_extra_leg, webtransport, ua_brand, bot_claim and bot_operator. notes_version identifies the vocabulary; the current version is 2. Notes do not enter signal_codes or change the default action. An unavailable note or a missing probe limits the evidence; it is not a failed token or a signals-service outage. Each note includes a value, rules_version and group. Your application can consider notes alongside its own history; observations in the same group may describe one cause and should not be treated as independent proof.

bot_claim reports a declared crawler whose network matches its operator (crawler_verified), a claim outside that network (crawler_unverified), a crawler with no supported operator check (crawler_unchecked), a declared tool (tool) or no such claim (none). bot_operator names the declared crawler’s operator. unavailable means the note could not be evaluated; none does not establish a human visitor. Even crawler_unverified is informational. Repeat-visit counts, farm-cluster detection and a raising signal for declared tools are not currently provided.

A missing browser probe can produce fp_probe_not_evaluated while the token remains valid and signals.status is evaluated. That operation still costs five signal credits. A note value of unavailable or unknown limits what the note establishes; it does not mean that the whole signal service was unavailable. The signal-code reference explains the new findings and note vocabulary.

Compare the combined outcomes

These ten synthetic cases use the same result contract. The ordinary case has no reported findings. The VPN case has an informational finding and no raising code.

Case email.status / email.action signals.status signal_codes Outer action
Ordinary signup deliverable / allow evaluated [] allow
Token used in a new operation deliverable / allow evaluated ["fp_token_reused"] allow_with_email_confirmation
Token absent deliverable / allow token_unusable ["fp_token_absent"] allow_with_email_confirmation
Informational VPN finding deliverable / allow evaluated [] allow
Signals service unavailable deliverable / allow unavailable [] allow
Email still being checked pending / allow_with_email_confirmation evaluated [] allow_with_email_confirmation
Email rejected undeliverable / reject evaluated [] reject
Browser automation finding deliverable / allow evaluated ["fp_probe_automation"] allow_with_email_confirmation
Informational tampering and notes deliverable / allow evaluated [] allow
Browser probe not evaluated; valid token deliverable / allow evaluated [] allow

The two allow outcomes for evaluated and unavailable signals do not establish the same thing. In the ordinary case, signals were evaluated and none raised the action. In the unavailable case, there was no completed signal assessment; email alone determined the recommendation.

Separate an unusable token from unavailable signals

An absent token gives signals.status: token_unusable, with fp_token_absent in signal_codes. An expired token also gives token_unusable, with fp_token_expired. These findings require confirmation when email would otherwise allow. They do not require you to treat the visitor as abusive: a blocked script or a long-running form can leave an ordinary visitor without a usable token.

When the signals service cannot evaluate the request, the combined endpoint can return HTTP 200 with signals.status: unavailable. In that outcome, the signals object contains only its status:

{
  "action": "allow",
  "signal_codes": [],
  "email": {"status": "deliverable", "action": "allow"},
  "signals": {"status": "unavailable"}
}

The service has not established that the visit is safe or that no VPN is present. The outer action follows email alone. Your application decides what access it permits while signal evidence is unavailable. If email required confirmation or rejection, that requirement would still stand.

Even an evaluated result can have gaps. A missing IP address, missing browser information or unavailable measurement can prevent individual comparisons. Where returned, not_evaluated and not_evaluated_classes describe those gaps. They are not extra email verdicts, and absence of a finding does not prove that its comparison passed.

An HTTP error that prevents you receiving a signup result is a separate integration outcome. Do not turn it into a made-up evaluated result. The API response contract distinguishes signal outcomes from request, connection and accounting errors.

A pending email does not mean pending signals

When email.status is pending, the signals are already final. The outer action remains allow_with_email_confirmation while the email check finishes. Preserve the check ID and retrieve GET /v1/signup-checks/{id} after email.retry_after_ms.

That read returns the current email result with the stored signals and recalculates the combined action. It does not assess the visit again. If email later returns allow, a stored raising signal still requires confirmation; if email returns reject, the combined action becomes reject.

pending is unfinished email work. A final unknown email result means the evidence did not settle the address. Neither state establishes that the address is invalid. Follow the returned action and use the email’s evidence to understand the limitation.

Replay the operation without creating a token-reuse event

If a response is lost, retry the same logical operation with its saved Idempotency-Key and the same request body. Within the 30-minute replay window, the API returns the saved check. The replay does not reevaluate the stored signals or create a second charge for the request. In a combined check, that saved record can include a newer email result; replay is not a promise that every response byte stays frozen while email finishes.

A new operation with the same token is a second presentation. It can add fp_token_reused and require confirmation, as in the first example. Generating a fresh key after a lost response changes the meaning of the request. Use the original key for a transport retry and a new token for a genuinely new signup attempt.

Reusing a key with a different body gives 409 idempotency_conflict. If the first request is still running, 409 request_in_progress tells you to wait according to Retry-After. The idempotency documentation explains the operation boundary. Reading the stored check with GET is also distinct from submitting a new check with the same token.

To connect these decisions to your own registration flow, follow the guide to stopping bot registrations. The Signup Protection page explains the product and the path to connecting your form and server.

Connect Signup Protection to your registration form.

Create an account, set up a page key and add the script and server call. Use the returned action in your application.