How to Stop Fake SaaS Signups With a Bot-Resistant Registration System
Fake SaaS signups are not merely untidy rows in a user table. Each automated submission can send an email, notify the owner, create a CRM contact, fire a webhook, start an onboarding sequence, consume analytics volume, and trigger paid infrastructure. The visible symptom may be a crowded inbox, but the real weakness is architectural: the system treats an unverified request as a customer too early.
The strongest general pattern is simple: verify first, create the user second. Collect the registration details, hold them in a restricted temporary record, send a short-lived verification code, and create the real authenticated user only after the code is accepted. Then, and only then, emit the verified signup event that downstream systems can trust.
This approach matches current OWASP guidance on email ownership verification, which says account use should not be enabled before verification. It also fits OWASP's broader advice to model multi-step signup flows as explicit server-side states rather than trusting the browser to enforce the sequence.
Figure 1. A verify-first gateway keeps anonymous automation outside the real user system.
What fake SaaS signup spam actually costs
OWASP classifies bulk account creation as an automated web threat known as OAT-019 Account Creation. The fake accounts may later be used for content spam, reputation manipulation, fraud, or other abuse. Even when the bot never signs in, the initial registration can still impose costs.
Typical consequences include:
A polluted production user table that makes customer counts unreliable
Repeated verification or welcome emails and unnecessary email-provider usage
False new-signup notifications that hide genuine leads
CRM contacts and sales tasks for people who do not exist
Distorted acquisition, activation, and conversion analytics
Webhooks, enrichment jobs, trial provisioning, or support workflows that run for bots
More personal data and security-sensitive records to retain, protect, audit, and eventually delete
The key design question is not only, “Can this address receive an email?” It is, “At what exact point does this submission become a real user and gain permission to affect the rest of the business?”
Why email confirmation alone does not stop signup spam
Many SaaS products already require email confirmation, yet their user table fills with unconfirmed accounts. That happens when the order is:
Create an authenticated user.
Send a confirmation message.
Wait for a link or code that may never be used.
This blocks portal access, but it does not block account creation. The bot has already produced a durable user record and may already have triggered an owner email, analytics event, CRM sync, webhook, welcome sequence, or trial allocation.
Disposable inboxes make the weakness clearer. OWASP notes that temporary email services can be used to bypass controls and recommends maintaining a known-domain list where appropriate, monitoring suspicious account-creation patterns, and preferring risk-based decisions over an inflexible block in every case. A bot with access to a throwaway mailbox may even complete basic email confirmation.
Email verification therefore proves a narrow fact: someone or something could retrieve a message sent to that inbox. OWASP explicitly cautions that email should be treated as a weak authentication factor. It does not prove that the registrant is human, unique, commercially valuable, or safe.
Figure 2. The cost of a fake signup grows when an untrusted form submission triggers downstream systems.
The stronger pattern: verify first, create the user second
Treat registration as a short, server-controlled workflow with two identities: a pending request and, after successful verification, a real user.
1. Validate and screen the initial submission
The user enters a full name, email address, and password. Validate length, syntax, required fields, password policy, and email handling on the server. OWASP recommends applying input validation as early as possible and enforcing both syntactic and business-context checks.
Check the honeypot, rate limits, and disposable-domain policy before sending an email. Return a neutral response that does not reveal whether an account already exists. Preserve the email address for display, but apply a deliberate canonical comparison policy. In particular, the domain is case-insensitive, while aggressive changes to the local part can create identity collisions.
2. Create a pending signup request, not a user
Write a temporary record with an awaiting_email_verification state and a short expiration time. A 10-minute lifetime is a practical default for many consumer and business SaaS products, but it should be tuned to delivery speed, audience, accessibility needs, and abuse risk.
If an active pending request already exists for the same canonical email, update or rotate it under a resend policy instead of creating unlimited rows. Keep a unique constraint on the canonical email or another transaction-safe rule so concurrent requests cannot produce duplicate accounts.
3. Generate and deliver a one-time code
Generate the six-digit code with a cryptographically secure random generator. Store only a protected verifier, never the plaintext code. Send the code to the submitted address and make it single-use.
A six-digit code has only one million possible values, so its safety depends on short expiry, strict attempt limits, request throttling, and secure comparison. Current NIST SP 800-63B-4 requires rate limiting for short authentication secrets and calls for resistant hashed storage. OWASP likewise says emailed tokens or codes should be random, securely stored, single-use, and time-limited.
4. Verify and create the account atomically
When the code arrives, the server should:
Load the pending request by an opaque request identifier, not by a client-controlled state flag.
Confirm that its state is awaiting verification and that it has not expired.
Apply a separate rate limit to code-verification attempts.
Compare the submitted code with its stored verifier using a safe comparison.
Increment the failure counter on a mismatch, then invalidate or delete the request when the limit is reached.
On success, create the confirmed user, consume the pending request, and record the transition in one transaction or equivalent atomic operation.
Start an authenticated session and rotate the session identifier.
OWASP's Business Logic Security guidance recommends storing multi-step workflow state on the server, rejecting replayed steps, expiring partial states, and making uniqueness checks plus account creation atomic. This prevents a user from skipping verification by calling the final endpoint directly or racing two successful requests.
5. Emit one trusted event after verification
After the account transaction succeeds, publish a single event such as user_signup_verified. That event can safely drive the owner notification, CRM record, analytics conversion, onboarding email, webhook, profile setup, and trial provisioning. Use idempotency so a retry cannot perform the same downstream action twice.
Figure 3. Verify-first registration contains anonymous traffic until it proves control of the email address.
The comparison is decisive: a create-first flow lets anonymous form traffic enter the real user system, while a verify-first flow contains it in an expiring staging area. The second design sharply reduces notification spam and cleanup work because unverified attempts never become customers.
What the pending signup table should contain
A pending-registration table should hold only what the workflow needs and should be inaccessible to ordinary users. A practical record includes:
An opaque request ID
The original email for delivery and display
A canonical email value for uniqueness and rate-limit checks
The submitted full name, subject to length and character validation
A password hash in a format accepted by the final authentication system
A salted code hash or keyed code verifier
A keyed digest of the IP address when an identifiable raw address is not required
The state, such as awaiting_email_verification
Verification failure count
Code-send count and last-sent time
Creation and expiration timestamps
Optional risk signals, stored only when justified and covered by the privacy notice
Never store the password or verification code in plaintext. OWASP recommends modern adaptive password hashing, with Argon2id as its preferred option where available. A code verifier needs different threat analysis because the original code has low entropy. Use a suitable password-hashing construction with a salt, or a keyed HMAC design with the key kept outside the database, then combine it with rate limiting and rapid expiry.
There is one implementation caveat. A pending password hash is useful only if the final identity layer can accept the same encoded hash. If a hosted authentication provider expects a new password rather than an imported hash, do not keep an encrypted, recoverable password simply to bridge the steps. Verify the email before the final password-creation step, or use a provider-supported pre-registration flow.
Add layered defenses without punishing real users
No single control stops every bot. The aim is to make automated registration progressively more expensive while keeping the normal path quick.
Block or score disposable email domains
Maintain a frequently updated blocklist or risk list for known temporary-email domains. Check the registrable domain after safe international-domain handling. Allow support overrides for legitimate users, and monitor false positives because domains appear, disappear, and change purpose.
For a low-risk free newsletter, you may score disposable addresses rather than block them. For a free trial with valuable credits, strict blocking or additional verification may be justified. This is a business-risk decision, not a universal email rule.
Rate limit by email and network source
Apply separate limits to initial signup requests, code sends, resends, and code verification. Combine per-email and per-IP limits so rotating one value is not enough. Where appropriate, add a device or session signal, but do not treat it as a stable identity.
A reasonable starting policy might allow three code sends per email in 15 minutes, ten signup starts per IP in one hour, and five incorrect code attempts per pending request. These are examples, not standards. Measure legitimate failure rates, shared-office and mobile-network traffic, IPv6 behavior, and attack patterns before tightening them. Edge limits are useful, but the application also needs feature-specific counters because only it understands the email and pending request.
Use an invisible honeypot
Add a form field that legitimate users do not see or fill. If it contains a value, reject the request silently or mark it as high risk. The field name should not announce its purpose, and accessibility testing must confirm that assistive technology is not invited to complete it.
A honeypot catches basic form-fill scripts at almost no human cost. It will not stop a bot that renders the page, inspects labels, or copies genuine browser behavior, so use it as a cheap first filter rather than a security boundary.
Escalate suspicious traffic to a challenge
If velocity, disposable-domain status, timing, or other signals look suspicious, add a bot challenge. Do not force every visitor through one unless the abuse level justifies the friction. CAPTCHA systems can themselves be defeated, as OWASP's CAPTCHA Defeat threat makes clear.
Any challenge decision must be validated on the server. For example, Cloudflare states that Turnstile client tokens must be checked server-side because client-only tokens can be forged, expire, and are single-use.
Security requirements that cannot be optional
The temporary table is not a security shortcut. It briefly contains authentication and personal data, so protect it to the same standard as the real identity system.
Keep privileged operations server-side. Account creation, confirmed-email flags, password-hash handling, code verification, notification emission, and any administrative authentication API must run in trusted backend code.
Never expose administrative credentials. Service-role keys, database owner credentials, HMAC keys, and mail-provider secrets must not be shipped in browser JavaScript or returned through an API.
Deny access by default. OWASP recommends least privilege, default denial, and permission checks on every request. Ordinary clients should never list or read pending signup records.
Use narrow database permissions. The application role should receive only the table, row, column, and operation access it needs. OWASP's Database Security guidance advises avoiding administrator accounts and granting minimum permissions.
Protect data in transit. Registration and verification endpoints must use HTTPS.
Rotate the session after sign-in. OWASP advises regenerating the session identifier when authentication changes a visitor from anonymous to authenticated.
Log events without logging secrets. Record outcomes, timestamps, masked identifiers, and correlation IDs. Never log passwords, verification codes, full verification URLs, or authentication tokens.
Minimise and expire personal data. If you replace a raw IP with a keyed digest, document the purpose, retention period, and key access. Pseudonymised data may still be personal data. The GDPR storage-limitation principle supports setting deletion deadlines, while the ICO notes that pseudonymisation reduces risk but does not automatically remove data from privacy law.
Stop unverified submissions from triggering business workflows
The cleanest event model has a hard boundary. signup_requested is an internal security workflow event. user_signup_verified is the business event.
Only the verified event should trigger:
The SaaS owner's “New signup” notification
A CRM contact, lead, or sales task
A product analytics signup or acquisition conversion
Welcome and onboarding campaigns
Customer-data-platform identification
Webhooks to partners or internal services
Free credits, trial resources, workspaces, or tenant provisioning
Lead scoring, enrichment, or account-research jobs
This rule prevents an untrusted form post from spreading across the stack. It also gives every team a clear contract: if a system consumes the verified event, it is dealing with a real authenticated account, not an abandoned attempt.
Clean up existing signup spam safely
Changing the flow prevents new pollution, but old unconfirmed accounts and stale pending requests still need attention.
First, define an evidence-based deletion rule. Useful indicators include an unconfirmed email, no successful sign-in, no billing relationship, no user-created content, no active session, suspicious domain or velocity patterns, and age beyond the normal verification window. Do not delete solely because an address looks unusual.
Next:
Back up or export the target IDs according to your incident and recovery policy.
Run a dry query and review the count plus a representative sample.
Exclude records with purchases, support history, regulatory retention requirements, or other legitimate activity.
Delete or disable confirmed spam in a controlled batch and remove dependent records according to referential rules.
Reconcile CRM contacts, onboarding jobs, analytics identities, webhooks, and email suppression records where necessary.
Schedule frequent deletion of expired pending requests and requests that exceed the failure limit.
Monitor verification completion, false-positive support cases, code delivery latency, resend rates, and attack volume after launch.
OWASP recommends expiring partial workflow states rather than allowing half-completed records to accumulate. Under privacy regimes such as the GDPR, personal data should also be kept no longer than necessary for its stated purpose.
Figure 4. Prevention and scheduled cleanup keep the authenticated user base trustworthy.
Keep normal sign-in methods working
A bot-resistant registration redesign does not require turning the rest of authentication upside down. Existing Google login, Apple login, enterprise single sign-on, magic links, password resets, and standard sign-in can continue through their established flows.
The tighter controls apply to account creation and any route that can silently create a local user. Social and enterprise identity providers may already perform their own email or domain verification, but the SaaS still needs a deliberate rule for when a first federated login becomes a local account and when downstream automations fire.
Legitimate users should experience one additional action at most: entering a code that arrives promptly. Returning visitors should not be asked to repeat signup verification. If risk is low, no visible challenge is needed.
Common implementation mistakes
Creating the real user first and calling it safe because email_confirmed is false
Sending the owner notification from the form handler instead of the verified-account event
Trusting a hidden browser field to say that verification happened
Storing a code in plaintext or logging it during debugging
Applying only an IP limit, which can punish shared networks and miss distributed bots
Applying only an email limit, which bots defeat by rotating addresses
Resetting the attempt counter whenever a new code is requested
Allowing multiple active codes for the same request without clearly invalidating older ones
Using a permanent disposable-domain list that nobody updates
Treating a hashed IP as anonymous and retaining it indefinitely
Creating the user and firing automations in separate non-idempotent steps
Exposing a service-role or administrative key to the browser
Key takeaways
A submitted registration form is a request, not a customer.
Keep pending registration state separate from the authenticated user table.
Use a random, single-use, short-lived code and limit both sends and verification attempts.
Combine disposable-email detection, rate limits, a honeypot, monitoring, and risk-based challenges.
Create the confirmed user and consume the pending request atomically.
Notify the owner and start downstream workflows only after verification succeeds.
Delete expired pending states and carefully remove legacy fake accounts.
Preserve a low-friction path for real customers and leave normal sign-in flows intact.
Frequently asked questions
Is email confirmation enough to stop fake SaaS accounts?
Not if the real account is created before confirmation. That design can still pollute the user database and trigger notifications or integrations. Email confirmation also does not stop bots that control disposable inboxes. It should be one layer inside a broader anti-abuse system.
Is a six-digit verification code secure?
It can be appropriate for short-lived email verification when it is generated securely, stored as a protected verifier, limited to one use, and protected by strict send and attempt limits. Six digits alone are not strong because there are only one million possibilities.
How long should a signup verification code last?
Ten minutes is a useful starting point, but there is no universal lifetime. Choose a window that accounts for normal email delivery and accessibility while limiting the attack window. Expire the entire pending request soon after the code or after a small number of failed attempts.
Should every disposable email domain be blocked?
Not automatically. Blocking is reasonable when a free account carries valuable credits or abuse risk, but any list can be incomplete or wrong. Lower-risk products may score the address, monitor it, or request another signal instead of rejecting it outright.
What should happen after too many wrong codes?
Invalidate or delete the pending request, apply a cooldown, and require a fresh signup attempt. Do not let a resend silently reset unlimited guessing. Keep user-facing messages neutral and log a masked security event.
Can the user be signed in automatically after verification?
Yes, for a normal signup flow, provided the server has successfully completed the account transaction, the verification is bound to the correct pending request, and the application creates a fresh authenticated session. Rotate the session identifier at this privilege change.
Does hashing the IP address solve privacy concerns?
It reduces exposure, especially when a keyed digest prevents simple reversal, but it may still be pseudonymous personal data. Collect it only for a stated security purpose, restrict access, rotate keys according to policy, and delete it when the rate-limit or investigation window ends.
How should existing fake users be removed?
Use multiple indicators, preview the target set, preserve recovery evidence, exclude any account with legitimate activity or retention obligations, then remove related downstream records in a controlled batch. Do not delete based only on an unusual-looking email address.
Conclusion
The most effective change is also the clearest: stop letting an unverified form submission become a user. Store it as a short-lived pending request, challenge it with a secure one-time code, and create the authenticated account only after verification succeeds.
That boundary turns every other control into a coherent system. Disposable-email checks reduce cheap inbox rotation. Rate limits and attempt caps constrain guessing and email flooding. Honeypots remove basic scripts without bothering people. Restricted temporary storage keeps half-finished workflows away from the real user base. Post-verification events protect the inbox, CRM, analytics, webhooks, onboarding, and infrastructure from traffic that never earned trust.
The result is not a harder signup experience. For a legitimate customer, it is still a familiar two-step flow. For a bot, it is a sequence of expiring, rate-limited gates with no real account and no downstream reward until verification is complete.