How to stop bots from registering on your site
Enforce limits on your server, check the email address and the visit behind the form, and control when a new account receives access. No single check establishes that a visitor is human.
Start at the point that creates an account
A script can post directly to your registration endpoint without opening the form. Protect that endpoint and the access it grants, including any free credits, messages or trial capacity.
- Apply server controls before creating the account or granting a benefit. Browser validation helps people correct input; it can be bypassed.
- Capture the visit when the form becomes visible, after applicable consent. Carry the token with the submitted form.
- Check from your server using the email address, token and request information your server observed.
- Apply the returned action. Your application sends confirmation mail and withholds the access that depends on it.
- Test normal and incomplete submissions, then monitor account creation and the use of trial benefits.
Which signup decision to apply
These are rules for combining the results, not a bot score. On the combined endpoint, read the outer action; the email result alone can miss a signal that requires confirmation.
| What the check returned | action | What to do here |
|---|---|---|
| Email action: reject | reject | Stop this attempt and explain the address problem. Signals cannot soften an email rejection. |
| Email action: confirmation required | allow_with_email_confirmation | Keep the account behind your mailbox-confirmation step. This includes pending and unknown email results; neither establishes that the address is invalid. |
| Email allows; a signal raises the action | allow_with_email_confirmation | Require mailbox confirmation. A missing, expired or reused token can cause this; it does not establish abuse. |
| Email allows; no signal raises the action | allow | Continue under your application rules. This also covers an unavailable signals service: the email decision stands, with no completed signal assessment. |
Apply controls on the server
Keep format checks in the form for immediate feedback, then validate again on the server. A browser email field checks the shape of an address; it cannot establish that a mailbox exists or stop a direct HTTP request. MDN explains why both validation steps are needed.
Set rate limits for the registration endpoint and confirmation-mail requests. Choose limits from your traffic and the cost of creating an account; an IP address may be shared by many legitimate visitors. A honeypot, an extra form field people are not expected to fill, can flag simple automated submissions. Check it on the server and test that autofill and assistive tools do not cause false rejections. More capable scripts may leave it empty.
Gate expensive trial features or other benefits until your requirements are met. Review bursts of account creation alongside what those accounts do next. These application controls follow the layered approach in OWASP’s anti-automation guidance. Rate limits, honeypots and trial restrictions are controls you implement; Signup Protection does not supply or configure them.
Get a token when the form becomes visible
Emvero Signup Protection compares the connection that obtained a page token with the request that submits your form. It adds evidence about that visit to the email decision. It does not return a human/bot boolean or a persistent visitor ID, and it does not count repeat accounts across visits.
Open Signup Protection in your account, enable the connection and wait for it to become ready. Add a page key for the HTTPS origins where your form appears. The fp_key_… page key is public and belongs in the browser; the secret emv_… API key stays on your server.
Load fp-token.js and the separately served fp-probe.js file with your account’s client code. Keep a hidden input, such as fp_token, inside the form. When the form becomes visible, create FpToken with the endpoint, page key and options, then call visitToken.fetch(vid). For an initially visible form, initialize when the page is ready; for a dialog, wait until it opens. Observe applicable consent. The option { quic: true } enables an optional WebTransport session.
vid is an opaque ID for the visit, passed as a string or a Promise of it. Do not use an account ID, email address or phone number. fetch(vid) resolves to the token string or an empty string. Your code writes that result into the hidden field; the library does not update the field. A missing or incompatible probe file leaves no token. An individual browser check can remain not evaluated while the token is usable.
Start collection while the person can see and fill the form. Collecting only at submission can trigger the fast-submission finding. Before a token expires on a long form, call visitToken.fetch() again and update the field. The probe keeps a usable previous token in page memory. Its first issue time carries forward only for the same IPv4 address or IPv6 /64, the same browser family, major version and platform, and a visit no older than 24 hours. Otherwise the timing starts again; a submission soon afterwards can produce the fast-submission finding. The current profile uses a 15-minute lifetime. Forward the field’s current opaque value with the form.
If the script or token request fails, the field can remain empty and the form can still submit. Handle that result on your server. The embedding instructions include the script, allowed origins, refresh and Content-Security-Policy setup. The probe file is updated separately from the token script. If that file is blocked or unavailable, no token is obtained. With a usable token, an individual browser check can still be reported as not evaluated.
Choose the server check
Use POST /v1/signup-checks to check the email and visit together. Send email, the hidden field as token, and the client ip, user_agent and available client_hints that your server observed. Behind a proxy, derive the client IP through your trusted proxy configuration; do not trust a form field that claims to contain it.
If you already check email elsewhere or will check it later, use POST /v1/signup-signals without email or variant. Its action is based on signals alone, so it cannot replace your email policy. Missing request information limits which comparisons can be made; it is not evidence that a comparison passed.
Evaluated signals cost five credits. The token_unusable and unavailable signal outcomes cost no signal credits; a combined check has a separate email charge. Enough balance for the requested reserves is required before the operation can proceed. See pricing and the credit calculator and the request and response contract.
Connect the action to account access
Apply the combined response’s outer action using the table above. Keep email.status, signals.status and action separate in your code. Signals can require email confirmation and cannot cause reject; they never relax the email decision.
For allow_with_email_confirmation, your application sends a confirmation message and checks its link or code before granting the relevant access. Emvero’s email check sends no message to the address being checked. Confirmation establishes control of a mailbox, not that an account belongs to a unique person. The signup form validation guide covers email results; verification and confirmation explains the two operations.
Browser-probe findings fp_probe_forged and fp_probe_automation require confirmation when email would otherwise allow. fp_probe_tampering is informational. The separate signals.notes array includes bot_claim and bot_operator as well as connection and browser observations. These notes distinguish declared crawlers and tools without changing the default action. Even a crawler claim outside its operator’s network is informational; a missing claim does not establish a human visitor.
Use signal_codes to see which findings raised the action, and signals.findings for the fuller evidence. Email reason_codes remain inside email. VPN, Tor, proxy and hosting findings are informational by default and do not raise the action on their own.
Handle incomplete checks and errors separately
An empty token produces signals.status: token_unusable with fp_token_absent and requires confirmation when email would otherwise allow. A signals-service outage produces signals.status: unavailable; the combined action follows email alone. Decide what access your own application permits during that outage. Unavailable signals do not establish that the visit is safe.
When email.status is pending, keep the confirmation requirement and retrieve GET /v1/signup-checks/{id} after email.retry_after_ms. The read returns stored signals with the current email result and recalculates the action. It does not evaluate the visit again. An unknown email result is uncertainty, not a finding that the address is invalid.
An HTTP error is a separate integration outcome. Fix malformed fields after 400; check the balance after 402; handle a disabled connection after 409 signup_protection_disabled. Respect Retry-After on retryable limits or an in-progress request. An accounting failure is not a signup verdict: preserve the attempt and follow the API error and retry instructions. A combined request can start evaluating signals before its email reserve fails, so 402 does not guarantee that no signal request ran.
Retry safely with the same operation key
Create one Idempotency-Key for each logical signup attempt, and retain the key and request body for a transport retry. Within the 30-minute replay window, the same key and body return the saved check without evaluating signals or charging again. A combined check can include a newer email result and a recalculated action; a replay is not a byte-for-byte snapshot. A changed body with that key returns 409 idempotency_conflict; while the first request runs, 409 request_in_progress tells you to wait according to Retry-After.
Sending the same token in a new operation is different from replaying the original operation. A second presentation to the same account within the token’s reuse window can add fp_token_reused and require confirmation. Do not generate a fresh key simply because the first response was lost. Use a new form token for a new attempt; the idempotency contract defines the retry boundary.
Test your page and server together
A ready connection, a created page key and a copied snippet are setup steps. Confirm installation with an evaluated check from your own form and server. The following are test cases, not measurements of real visitors:
- Complete a normal form after capture starts. Check that your server receives the token and observed request fields, then applies the outer action. A normal-looking visit is not guaranteed to return
allow. - Submit without a token. Confirm that your application receives
token_unusableand provides the required mailbox-confirmation path. - Simulate
unavailablesignals and apendingemail in your test environment. Check the outage policy and stored-check polling separately. - Repeat the identical request with its saved key, then test a new operation with the same token. Verify the difference between a replay and
fp_token_reused. - Test an address rejection and an informational network finding. An email rejection must stand; a network label alone must not become an Emvero rejection.
Monitor decisions, incomplete checks, confirmation completion and access to trial benefits. Keep tokens and secret keys out of logs. Adjust your application’s limits using observed abuse and legitimate signup failures; Emvero has no custom signup-policy editor or ready-made CMS connector.
Connect your registration flow
Create an Emvero account, open Signup Protection and add a page key. Then connect your form and server using the installation instructions.