As cyber threats evolve, protecting your website from malicious bots and automated attacks has become essential. If you're using Fastly CDN, you can integrate Procaptcha to intercept suspicious traffic and display a privacy-friendly CAPTCHA challenge. Unlike Google's reCAPTCHA, Procaptcha is designed with privacy at its core and provides a GDPR-compliant solution that respects user privacy.
In this guide, we'll walk you through how to implement Procaptcha with Fastly to protect high-risk requests while maintaining excellent performance and user experience.
Fastly is a modern content delivery network (CDN) and edge cloud platform that helps websites deliver content faster to users worldwide. It provides powerful features including:
By combining Fastly's edge capabilities with Procaptcha's privacy-focused bot detection, you create a powerful bot defense system that operates at the network edge.
Before you begin, make sure you have:
SITE_KEY and SECRET_KEY from the portalThere are two main ways to integrate Procaptcha with Fastly:
This approach uses Fastly to detect suspicious traffic and inject the Procaptcha challenge on the client side.
This approach uses Fastly Compute to handle the entire CAPTCHA flow at the edge, including verification.
We'll cover both approaches in this guide.
This method uses Fastly VCL to detect high-risk requests and inject Procaptcha into your web pages.
First, we'll set up VCL logic to identify suspicious traffic patterns. Add this to your Fastly VCL service:
sub vcl_recv {
# Initialize risk score
declare local var.risk_score INTEGER;
set var.risk_score = 0;
# Check for suspicious user agents
if (req.http.User-Agent ~ "(?i)(bot|crawler|spider|scraper)") {
set var.risk_score = var.risk_score + 50;
}
# Check request rate (requires Fastly rate limiting)
if (ratelimit.check_rate("client_" + client.ip,
"100",
"60s",
"100",
"100",
"",
"client_" + client.ip)) {
set var.risk_score = var.risk_score + 30;
}
# Check for missing common headers
if (!req.http.Accept-Language) {
set var.risk_score = var.risk_score + 20;
}
# Check for suspicious referrers or missing referrer on sensitive paths
if (req.url ~ "^/(login|register|checkout)" && !req.http.Referer) {
set var.risk_score = var.risk_score + 25;
}
# If risk score is high, flag for CAPTCHA
if (var.risk_score > 50) {
set req.http.X-Needs-Captcha = "true";
}
}When a high-risk request is detected, inject the Procaptcha script and challenge:
sub vcl_deliver {
# Only inject for HTML responses flagged as high-risk
if (req.http.X-Needs-Captcha == "true" &&
resp.http.Content-Type ~ "text/html") {
# Inject Procaptcha bundle script into <head>
set resp.http.Content-Type = "text/html; charset=utf-8";
# Use synthetic response with CAPTCHA challenge
if (resp.status == 200) {
return(deliver(captcha));
}
}
}
# Define CAPTCHA challenge page
sub vcl_synth {
if (obj.status == 950) {
set resp.status = 200;
set resp.http.Content-Type = "text/html; charset=utf-8";
# Generate HTML with Procaptcha widget
# The synthetic response includes:
# - Procaptcha bundle script in the head
# - A centered container with the captcha form
# - Auto-submit JavaScript when captcha completes
# - Styling for a clean, centered verification page
synthetic({"<!DOCTYPE html>..."}); # Full HTML omitted for brevity
return(deliver);
}
}Important: Replace YOUR_SITE_KEY with your actual Procaptcha site key.
When the user completes the CAPTCHA, verify the response by calling the Procaptcha API from VCL. First, configure a backend for the Procaptcha API in your Fastly service settings:
Backend Configuration:
procaptcha_apiapi.prosopo.io443Then add this VCL code to verify the token:
sub vcl_recv {
# Check if CAPTCHA response is present
if (req.http.X-Needs-Captcha == "true" &&
req.request == "POST" &&
req.http.procaptcha-response) {
# Store the original request
set req.http.X-Original-URL = req.url;
set req.http.X-Captcha-Token = req.http.procaptcha-response;
# Prepare verification request to Procaptcha API
set req.backend = procaptcha_api;
set req.url = "/siteverify";
set req.http.Host = "api.prosopo.io";
set req.http.Content-Type = "application/json";
# Build JSON body for verification
# Note: Replace YOUR_SECRET_KEY with your actual secret key
set req.http.X-Verify-Body = {"secret":"YOUR_SECRET_KEY","token":"} + req.http.X-Captcha-Token + {""};
return(pass);
}
}
sub vcl_pass {
# Send the verification request with JSON body
if (req.url == "/siteverify" && req.http.X-Verify-Body) {
set bereq.method = "POST";
set bereq.body = req.http.X-Verify-Body;
}
}
sub vcl_deliver {
# Handle verification response
if (req.url == "/siteverify" && req.http.X-Captcha-Token) {
# Parse the verification result
# The API returns JSON like: {"verified": true/false}
if (resp.body ~ "\"verified\":\s*true") {
# Verification successful - set cookie and redirect to original URL
set resp.http.Set-Cookie = "captcha_verified=true; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600";
set resp.http.Location = req.http.X-Original-URL;
set resp.status = 302;
return(deliver);
} else {
# Verification failed - show error
error 403 "CAPTCHA verification failed";
}
}
}Important Notes:
YOUR_SECRET_KEY with your actual Procaptcha secret keyhttps://api.prosopo.io/siteverify{"secret": "YOUR_SECRET", "token": "PROCAPTCHA_RESPONSE"}{"verified": true}For more sophisticated control, use Fastly Compute to handle the entire CAPTCHA flow at the edge.
Create a new Fastly Compute service using JavaScript:
$ fastly compute init
# Choose JavaScript as your languageCreate your main Compute handler (src/index.js):
/// <reference types="@fastly/js-compute" />
import { env } from "fastly:env";
// Procaptcha configuration
const PROCAPTCHA_SITE_KEY = env('PROCAPTCHA_SITE_KEY') || 'YOUR_SITE_KEY';
const PROCAPTCHA_SECRET_KEY = env('PROCAPTCHA_SECRET_KEY') || 'YOUR_SECRET_KEY';
const PROCAPTCHA_VERIFY_URL = 'https://api.prosopo.io/siteverify';
// Risk scoring function
function calculateRiskScore(request) {
let score = 0;
const userAgent = request.headers.get('User-Agent') || '';
const referer = request.headers.get('Referer') || '';
const acceptLanguage = request.headers.get('Accept-Language') || '';
// Check user agent
if (/bot|crawler|spider|scraper/i.test(userAgent)) {
score += 50;
}
// Check for missing headers
if (!acceptLanguage) {
score += 20;
}
// Check sensitive paths without referrer
const url = new URL(request.url);
if (/\/(login|register|checkout)/.test(url.pathname) && !referer) {
score += 25;
}
return score;
}
// Generate CAPTCHA challenge page
function getCaptchaChallenge(request) {
const url = new URL(request.url);
const returnUrl = url.pathname + url.search;
// Build HTML page with Procaptcha widget
// Includes: script bundle, responsive styling, auto-submit form
const html = buildCaptchaHTML(PROCAPTCHA_SITE_KEY, returnUrl);
return new Response(html, {
status: 200,
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store, no-cache, must-revalidate',
}
});
}
// Helper function to build the CAPTCHA challenge HTML
function buildCaptchaHTML(siteKey, returnUrl) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verification Required</title>
<script src="https://js.prosopo.io/js/procaptcha.bundle.js" async defer><\/script>
</head>
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<div style="background: white; padding: 2.5rem; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); max-width: 500px; text-align: center;">
<h1 style="color: #333; margin-bottom: 1rem; font-size: 1.8rem;">🔒 Security Verification</h1>
<p style="color: #666; margin-bottom: 2rem; line-height: 1.6;">We've detected unusual activity. Please verify to continue.</p>
<form id="captcha-form" action="/verify-captcha" method="POST">
<input type="hidden" name="return_url" value="${returnUrl}">
<div class="procaptcha" data-sitekey="${siteKey}"></div>
</form>
<p style="font-size: 0.85rem; color: #999; margin-top: 1.5rem;">
This protects our site while respecting your privacy.
<a href="https://prosopo.io/" target="_blank">Learn more</a>
</p>
</div>
<script>
document.addEventListener('procaptcha-success', function() {
document.getElementById('captcha-form').submit();
});
<\/script>
</body>
</html>`;
}
// Verify CAPTCHA token with Procaptcha API
async function verifyCaptchaToken(token) {
try {
const response = await fetch(PROCAPTCHA_VERIFY_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
secret: PROCAPTCHA_SECRET_KEY,
token: token,
}),
backend: 'procaptcha_api', // Configure this backend in fastly.toml
});
if (!response.ok) {
return { verified: false, error: 'Verification request failed' };
}
const result = await response.json();
return { verified: result.verified === true, data: result };
} catch (error) {
console.error('CAPTCHA verification error:', error);
return { verified: false, error: error.message };
}
}
// Main request handler
async function handleRequest(event) {
const request = event.request;
const url = new URL(request.url);
// Handle CAPTCHA verification endpoint
if (url.pathname === '/verify-captcha' && request.method === 'POST') {
const formData = await request.formData();
const token = formData.get('procaptcha-response');
const returnUrl = formData.get('return_url') || '/';
if (!token) {
return new Response('Missing CAPTCHA response', { status: 400 });
}
const verification = await verifyCaptchaToken(token);
if (verification.verified) {
// Set a cookie to remember verified users
const response = Response.redirect(url.origin + returnUrl, 302);
response.headers.set('Set-Cookie',
'captcha_verified=true; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600'
);
return response;
} else {
return new Response('CAPTCHA verification failed', { status: 403 });
}
}
// Check if user already verified
const cookies = request.headers.get('Cookie') || '';
if (cookies.includes('captcha_verified=true')) {
// User already verified, pass through to origin
return fetch(request, {
backend: 'origin_server',
});
}
// Calculate risk score
const riskScore = calculateRiskScore(request);
// If high risk, show CAPTCHA challenge
if (riskScore > 50) {
return getCaptchaChallenge(request);
}
// Low risk, pass through to origin
return fetch(request, {
backend: 'origin_server',
});
}
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));Update your fastly.toml to include the Procaptcha API backend:
# fastly.toml
manifest_version = 2
name = "procaptcha-protection"
description = "Fastly Compute service with Procaptcha integration"
authors = ["your-email@example.com"]
language = "javascript"
[local_server]
[local_server.backends]
[local_server.backends.origin_server]
url = "https://your-origin-server.com"
[local_server.backends.procaptcha_api]
url = "https://api.prosopo.io"
override_host = "api.prosopo.io"
[setup]
[setup.backends]
[setup.backends.origin_server]
address = "your-origin-server.com"
port = 443
[setup.backends.procaptcha_api]
address = "api.prosopo.io"
port = 443
override_host = "api.prosopo.io"Configure your Procaptcha keys as environment variables:
$ fastly compute publish
# During setup, add environment variables:
# PROCAPTCHA_SITE_KEY=your_site_key_here
# PROCAPTCHA_SECRET_KEY=your_secret_key_here$ fastly compute publishYour Fastly Compute service will now:
Adjust your risk score thresholds based on your traffic patterns:
// Conservative (more challenges)
if (riskScore > 30) { showCaptcha(); }
// Moderate (balanced)
if (riskScore > 50) { showCaptcha(); }
// Lenient (fewer challenges)
if (riskScore > 70) { showCaptcha(); }Monitor your false positive rate and adjust accordingly.
Combine Procaptcha with Fastly's rate limiting to prevent abuse:
if (ratelimit.check_rate("client_" + client.ip,
"100", # bucket size
"60s", # time window
"100", # requests allowed
"100", # penalty
"",
"client_" + client.ip)) {
error 950 "Rate limit exceeded";
}Don't challenge all users equally. Consider:
Track key metrics:
Use Fastly's real-time analytics to monitor these metrics and adjust your configuration.
Always explain why users see a CAPTCHA challenge:
<p>We've detected unusual activity from your connection...</p>
<p>This helps protect our site from automated attacks.</p>
<p>Your privacy is respected - no personal data is collected.</p>Clear communication reduces user frustration and abandonment.
Problem: The Procaptcha widget doesn't appear on the challenge page.
Solutions:
https://js.prosopo.io/js/procaptcha.bundle.jsSITE_KEY is correctprocaptcha div has the correct data-sitekey attributeProblem: CAPTCHA verification endpoint always returns false.
Solutions:
SECRET_KEY is correctapi.prosopo.io is configured correctlyProblem: Legitimate users see CAPTCHA challenges too often.
Solutions:
Problem: CAPTCHA integration slows down your site.
Solutions:
Integrating Procaptcha with Fastly CDN provides a powerful, privacy-focused solution for protecting your website from bot attacks. By implementing bot detection at the edge, you reduce server load, improve performance, and create a better experience for legitimate users.
Unlike traditional CAPTCHA solutions that track users and collect data, Procaptcha respects user privacy while providing effective protection against:
Whether you choose the VCL approach for simplicity or the Compute approach for flexibility, Procaptcha integrates seamlessly with Fastly's edge platform to deliver security without compromising user experience or privacy.
Ready to get started? Sign up for a free Procaptcha account and start protecting your website today.
If you need assistance implementing Procaptcha with Fastly CDN, please get in touch below to arrange a demo or discuss your requirements.

Wed, 20 Sept 2023

Tue, 20 Feb 2024

Thu, 22 Feb 2024

Tue, 09 Apr 2024

Sat, 13 Apr 2024

Mon, 10 Nov 2025

Sun, 18 Feb 2024

Fri, 15 Mar 2024

Mon, 18 Mar 2024