How to Stop PHP Form Spam (Without a Plugin)
If your PHP mail script is being abused by bots, the fix is to stop the bot before mail() runs. Server-side CAPTCHA verification in a few lines of PHP, with the Procaptcha API.

If your PHP contact form is being abused — mail() firing thousands of times a day, your inbox filled with junk, your shared host warning you about outbound mail limits — the fix isn't in PHP. The form is doing exactly what you wrote it to do. The problem is who is submitting it.
This is a server-side problem with a server-side answer: verify that the request came from a real person (or a trusted agent) before mail() runs. The rest of this post walks through how to do that in a few lines of PHP using the Procaptcha API — no WordPress plugin required, no JavaScript framework, no rewriting of your existing form.
Why "spam PHP" isn't really a PHP problem
PHP forms get spammed because they're easy targets:
- The HTML form is on a static URL — a bot can POST to it from anywhere.
- The backend (
mail(), PDO, an HTTP webhook, anything) trusts the request and runs. - Standard PHP doesn't have any concept of "this came from a browser, not a script".
Adding a server-side check is the only real fix. Honeypot fields catch the laziest bots. Time-on-page checks catch a few more. Modern bots solve both within a year of any new technique appearing in the wild — and the bots written for AI training, those used by residential-proxy networks, and the agentic shopping bots that solve image puzzles in 200ms don't care about any of them.
A CAPTCHA that runs server-side and that the bot can't pre-solve is the only durable answer.
The three-line fix
The shape of the integration is the same for every PHP setup:
- Embed the Procaptcha widget in your form.
- On submit, the widget returns a token in a hidden field.
- Your PHP handler POSTs that token to Procaptcha's verification endpoint before running
mail()or anything else.
If the token is invalid, you reject the submission. If it's valid, you proceed.
<?php
// process-form.php
$token = $_POST['procaptcha-response'] ?? '';
if (empty($token)) {
http_response_code(400);
exit('Verification missing.');
}
$verify = curl_init('https://api.prosopo.io/siteverify');
curl_setopt_array($verify, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'secret' => getenv('PROCAPTCHA_SECRET'),
'token' => $token,
]),
]);
$response = json_decode(curl_exec($verify), true);
curl_close($verify);
if (empty($response['verified'])) {
http_response_code(400);
exit('Verification failed.');
}
// Only now do we call mail().
$name = strip_tags($_POST['name'] ?? '');
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$message = strip_tags($_POST['message'] ?? '');
mail(
'inbox@example.com',
'Contact form: ' . $name,
$message,
"From: $email"
);
echo 'Thanks — we got your message.';
The widget on the form side is one script tag plus a <div>:
<form method="POST" action="/process-form.php">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<div data-procaptcha
data-sitekey="<?= htmlspecialchars(getenv('PROCAPTCHA_SITE_KEY')) ?>"></div>
<button type="submit">Send</button>
</form>
<script src="https://js.prosopo.io/js/procaptcha.bundle.js" async defer></script>That's the whole integration. The mail() call never runs unless verification passes — so the bot can spray a thousand POSTs at process-form.php and not one of them turns into an outbound email.
What about Composer / Laravel / Symfony?
The pattern is identical — POST to /siteverify with the token, fail closed if it doesn't come back verified. In Laravel you'd typically wrap it in a form-request rule; in Symfony, a validator constraint. The HTTP call itself is the same.
If you want a more idiomatic integration, the official PHP SDK is on Packagist (search for prosopo/procaptcha).
Why this is the right layer to fix it
PHP form spam often gets "solved" by adding mail-side rules — SPF, DKIM, DMARC, rate-limiting the outbound queue. None of those address the actual problem. Your mail server is doing the right thing: it's sending the mail your application asked it to send. The application is asking it to send junk because the application can't tell humans from bots.
Verifying before mail() runs:
- Restores your sending reputation (no more bursts of bot-driven outbound mail).
- Stops shared-host abuse warnings.
- Cuts CRM and autoresponder costs (bots aren't subscribing to anything).
- Stops
Reply-Toinjection attacks and header-spoofing exploits dead in their tracks — the bot can't even submit the form to attempt them.
Pricing and free tier
Procaptcha covers 10,000 verifications a month on the free tier. For a typical PHP contact form that's well above what real traffic uses, so the integration costs nothing for most sites. Paid plans start at $39/month and unlock the Spam Filter — Gmail dot-trick blocking, VPN/Tor traffic filters, and disposable-domain detection — for forms that need belt-and-braces protection.
See the pricing page for the full breakdown, or get in touch via the form below if you want a hand wiring it up.
Related
- Spam bot protection — the broader use case across every form type and platform.
- How Prosopo Spam Filter works — the email/traffic filter that pairs with the CAPTCHA.
- Best CAPTCHA in 2026 — comparison if you're evaluating Procaptcha against alternatives.
Stop PHP form spam at the source
If your PHP forms are being abused by bots, we can help. Get in touch and we'll arrange a quick technical demo of how Procaptcha integrates with any PHP backend.




