Bots sign up and spam through forms
How to ask WebShield for a score of every sign-up or lead from your backend and filter out fake accounts, spam and SMS pumping.
Through sign-up and lead forms bots create fake accounts, send junk and trigger paid SMS to random numbers. The form fields alone cannot tell a bot from a person, and a captcha checks every visitor alike.
The fix is form protection: a tag on your pages observes the visit, and at submission your backend asks us for a score and gets a verdict. What to do with the verdict is up to your code.
What you get: every sign-up or lead gets an allow, challenge or deny verdict
with reasons; bots are filtered out, and doubtful visitors get a check — only them, not
everyone.
You need: access to the site’s code — both the page template and the form handler on
the server. You don’t need to move the domain to us. Time: an hour or two including
the rollout.
1. Create a project
Section titled “1. Create a project”Services → Form protection → Create project. In Allowed site addresses enter
the scheme and domain without a path — https://example.com and
https://www.example.com. You get a site key and ready-to-paste tag code.
The backend needs an API token with the Form protection: write scope. Keep it with your application secrets, next to the database password.
2. Put the tag on your pages
Section titled “2. Put the tag on your pages”In the <head> of every page, not only the one with the form:
<script src="https://af.webshield.pro/t/v1.js?k=YOUR_KEY" data-forms="#signup" async></script>data-forms lists the CSS selectors of your forms, comma-separated. The tag adds a hidden
ws_sid field — the visitor’s session ID — to each of them. The tag goes on every page
because the visit before the form is part of the score too.
Check: open the page with the form and find <input type="hidden" name="ws_sid"> in
the developer tools — within a second or two it gets a value.
3. Ask for a score from your backend
Section titled “3. Ask for a score from your backend”In the form handler, before creating the user and sending an SMS, send ws_sid to us.
PHP:
function ws_decision(string $sid, string $event): string{ $ch = curl_init('https://webshield.pro/api/v1/antifraud/score'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('WS_TOKEN'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'site_key' => getenv('WS_SITE_KEY'), 'session_id' => $sid, 'event' => $event, ]), ]); $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch); // Our failure must not break your sign-up. if ($body === false || $code !== 200) { return 'allow'; } return json_decode($body, true)['decision'] ?? 'allow';}
$decision = ws_decision($_POST['ws_sid'] ?? '', 'signup');Node.js:
async function wsDecision(sid, event) { try { const resp = await fetch("https://webshield.pro/api/v1/antifraud/score", { method: "POST", headers: { Authorization: `Bearer ${process.env.WS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ site_key: process.env.WS_SITE_KEY, session_id: sid, event }), signal: AbortSignal.timeout(3000), }); if (!resp.ok) return "allow"; return (await resp.json()).decision ?? "allow"; } catch { // Our failure must not break your sign-up. return "allow"; }}
const decision = await wsDecision(req.body.ws_sid ?? "", "signup");A Python example is in the reference.
event names the action, and you choose it: signup, login, contact, sms. Give
different forms different names: rates are counted for each separately.
4. Watch first, block later
Section titled “4. Watch first, block later”Don’t turn on refusals on day one. For a week just record the verdict next to each sign-up and see whom it would cut off. The report in the project card shows the same from our side: the share of automation, the reasons that fire, and recent sessions.
Once the verdicts earn your trust, act on them:
if ($decision === 'deny') { http_response_code(403); exit('Sign-up rejected.');}if ($decision === 'challenge') { // No outright refusal: email confirmation, manual moderation, // no SMS until confirmed — whatever is cheaper for you and doesn't bother a person.}// allow — sign up as usual.The most valuable rule against SMS pumping: send a paid message on nothing but
allow. For challenge an email or a visitor check will do.
5. Turn on the check for doubtful visitors
Section titled “5. Turn on the check for doubtful visitors”The project card has a single switch — whether to show the visitor a check. When it is on, on a doubtful verdict the tag itself shows a short picture task over the page and submits the form once it is solved. Your code changes nothing: the next score request already accounts for the solved task.
While the check is off, the API verdict is still real — the switch only affects what the visitor sees.
- Sign up yourself from a regular browser: the verdict is
allow. - Submit the form from
curlwithoutws_sid: the verdict ischallengewith theno_sessionreason — a request that bypasses the browser does not go unnoticed. - Sessions and events show up in the project report.
When something is off
Section titled “When something is off”| Symptom | Cause and fix |
|---|---|
Everyone gets challenge with no_session |
ws_sid does not reach the backend. The selector in data-forms does not match the form, or your JavaScript submits the form without the hidden field — see manual form binding. |
403 from the API |
The token lacks the Form protection: write scope or has expired. |
The report is empty, ws_sid stays blank |
The page address is not in Allowed site addresses — check the scheme and www. |
| A second form on the page isn’t protected | The tag is installed twice. Keep one and list both forms in data-forms, comma-separated. |
A mobile app gets challenge |
The app has no tag and so no session. Give it a separate handler with its own logic. |
What next
Section titled “What next”- Form and sign-up protection — every response field, a user identifier for a sharper score, and outcome feedback that tunes the score to your site.
- Is the form on a static site hosted with us? Protection is built in there — see A contact form without a backend.
- WordPress is flooded with password guessing — if bots don’t just sign up but also guess passwords.