Cloudflare Turnstile NextJS: Invalid Token Error on Repeated Submissions
Was used in notlink — a blazingly fast url shortener ever built with rust programming language. Check: https://notl.ink The “Invalid token” error occurs because the Turnstile token is being reused for multiple submissions. Cloudflare Turnstile tokens are single-use; once validated, they can’t be used again. Here’s how to fix it: Reset the Turnstile widget after each submission Update your frontend code to reset the Turnstile widget and clear the token state after submission: // Add a ref to the Turnstile component const turnstileRef = useRef(); async function handleShorten(val: string) { // ... existing code ... try { const response = await fetch(/api/shorten, { /* ... */ }); const data: ShortURLResponse = await response.json(); // Reset Turnstile after successful submission turnstileRef.current?.reset(); setTurnstileToken(""); setTurnstileStatus("required"); } catch (error) { console.error('Error:', error); } finally { setLoading(false); // Ensure Turnstile is reset even if there's an error turnstileRef.current?.reset(); setTurnstileToken(""); setTurnstileStatus("required"); } } // Update your Turnstile component with the ref ref={turnstileRef} siteKey={process.env.TURNSTILE_SITE_KEY!} // ... other props ... /> (Optional) Clear token state on expiration/error Enhance your Turnstile event handlers: // ... other props ... onExpire={() => { setTurnstileStatus("expired"); setTurnstileError("Security check expired. Please verify again."); setTurnstileToken(""); // Clear expired token }} onError={() => { setTurnstileStatus("error"); setTurnstileError("Security check failed. Please try again."); setTurnstileToken(""); // Clear invalid token }} /> Why this works: Each submission now requires a fresh Turnstile verification The token state is cleared after submission/errors/expiration The Turnstile widget is reset to force a new challenge Additional recommendations for your backend: Ensure your idempotencyKey implementation matches Turnstile's requirements Consider adding rate limiting to prevent abuse Verify the token expiration time (typically 5 minutes) By implementing these changes, users will need to complete a new Turnstile verification for each submission, preventing token reuse and the “Invalid token” error.

Was used in notlink — a blazingly fast url shortener ever built with rust programming language. Check: https://notl.ink
The “Invalid token” error occurs because the Turnstile token is being reused for multiple submissions. Cloudflare Turnstile tokens are single-use; once validated, they can’t be used again. Here’s how to fix it:
- Reset the Turnstile widget after each submission
Update your frontend code to reset the Turnstile widget and clear the token state after submission:
// Add a ref to the Turnstile component
const turnstileRef = useRef();
async function handleShorten(val: string) {
// ... existing code ...
try {
const response = await fetch(/api/shorten
, { /* ... */ });
const data: ShortURLResponse = await response.json();
// Reset Turnstile after successful submission
turnstileRef.current?.reset();
setTurnstileToken("");
setTurnstileStatus("required");
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
// Ensure Turnstile is reset even if there's an error
turnstileRef.current?.reset();
setTurnstileToken("");
setTurnstileStatus("required");
}
}
// Update your Turnstile component with the ref
ref={turnstileRef}
siteKey={process.env.TURNSTILE_SITE_KEY!}
// ... other props ...
/>
- (Optional) Clear token state on expiration/error
Enhance your Turnstile event handlers:
// ... other props ...
onExpire={() => {
setTurnstileStatus("expired");
setTurnstileError("Security check expired. Please verify again.");
setTurnstileToken(""); // Clear expired token
}}
onError={() => {
setTurnstileStatus("error");
setTurnstileError("Security check failed. Please try again.");
setTurnstileToken(""); // Clear invalid token
}}
/>
Why this works:
Each submission now requires a fresh Turnstile verification
The token state is cleared after submission/errors/expiration
The Turnstile widget is reset to force a new challenge
Additional recommendations for your backend:
Ensure your idempotencyKey implementation matches Turnstile's requirements
Consider adding rate limiting to prevent abuse
Verify the token expiration time (typically 5 minutes)
By implementing these changes, users will need to complete a new Turnstile verification for each submission, preventing token reuse and the “Invalid token” error.