Troubleshooting
Solutions for common Crovly integration issues.
Content Security Policy (CSP)
If your site uses a Content Security Policy, add Crovly's domains to allow the widget to load and communicate with the API.
Content-Security-Policy:
script-src 'self' https://get.crovly.com;
connect-src 'self' https://api.crovly.com;
worker-src 'self' blob:;The worker-src blob: directive is required because Crovly uses a Web Worker to solve Proof of Work challenges without blocking the main thread.
If you use a meta tag instead of a header:
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' https://get.crovly.com; connect-src 'self' https://api.crovly.com; worker-src 'self' blob:;">For self-hosted setups, replace the domains with your own API and CDN URLs.
CORS Issues
If you see CORS errors in the browser console, check:
- Your site key is valid. Invalid site keys return a
403without CORS headers. - The domain matches. The site key is bound to the domain you registered in the dashboard.
localhostis allowed for all site keys during development. - You are not calling the API directly from the browser. The
/verify-tokenendpoint is for server-to-server calls only — it does not support CORS. Call it from your backend.
Widget Not Loading
Symptom: The #crovly-captcha div stays empty.
Check these:
- Script tag is present and points to
https://get.crovly.com/widget.js. data-site-keyis set on the script tag or the container div (either works).- The container div exists with
id="crovly-captcha"orclass="crovly-captcha". The widget uses a MutationObserver, so the div can be added dynamically (React, Vue, WordPress AJAX, etc.). - Ad blockers are not blocking the script. Some aggressive ad blockers flag captcha scripts.
- Check the console for JavaScript errors — especially CSP violations (see above).
<!-- Option 1: site key on script tag -->
<script src="https://get.crovly.com/widget.js"
data-site-key="YOUR_KEY"></script>
<div id="crovly-captcha"></div>
<!-- Option 2: site key on container div (useful for SPAs) -->
<script src="https://get.crovly.com/widget.js"></script>
<div id="crovly-captcha" data-site-key="YOUR_KEY"></div>
<!-- Wrong: no site key anywhere -->
<script src="https://get.crovly.com/widget.js"></script>
<div id="crovly-captcha"></div>Invisible Mode Not Working
Symptom: Using data-size="invisible" but the widget is still visible, or the form submits without a token.
Check these:
- The
data-size="invisible"attribute is on the script tag, not the div. - The
#crovly-captchadiv is still present in the DOM (it is required even in invisible mode). - The form submission waits for the token. In invisible mode, verification starts on page load but may not be complete by the time the user submits. Listen for
onVerifybefore enabling the submit button.
<script src="https://get.crovly.com/widget.js"
data-site-key="YOUR_KEY"
data-size="invisible"></script>
<form action="/submit" method="POST">
<div id="crovly-captcha"></div>
<input type="email" name="email" required />
<button type="submit">Subscribe</button>
</form>Token Expiry
Crovly tokens expire 5 minutes after they are issued. If a user fills out a long form and submits after the token expires, the backend /verify-token call will return:
{
"success": false,
"error": "Token expired"
}Solutions:
- Listen for the
onExpirecallback and re-run verification:
Crovly.render('#crovly-captcha', {
siteKey: 'YOUR_KEY',
onExpire: () => {
Crovly.reset('#crovly-captcha');
},
});- For long forms, place the captcha near the submit button so it runs closer to submission time.
"Verification Failed" Errors
If /verify-token returns { "success": false }, check:
- API key is correct. Use the API key (starts with
crvl_secret_), not the site key. - Token is fresh. Tokens expire after 5 minutes and can only be used once.
- IP matches. If you pass
expectedIp, it must match the IP that solved the challenge. Users behind load balancers or proxies may have a different IP than expected. - Token is not modified. Pass the token exactly as received from the widget.
// Correct: use the API key
const res = await fetch('https://api.crovly.com/verify-token', {
method: 'POST',
headers: {
'Authorization': 'Bearer crvl_secret_xxx', // API key, not site key
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
});Self-Hosted Setup Issues
If you are running a self-hosted Crovly deployment:
- Set
data-api-urlon the widget to point to your self-hosted API. - Ensure Redis is accessible from the Crovly server. Nonce caching and rate limiting require Redis.
- Ensure PostgreSQL is accessible for storing verification logs and site data.
- Set all environment variables as documented in the Self-Hosting guide.
- Check the health endpoint at
/api/healthto verify the server is running.
Common Mistakes
| Mistake | Fix |
|---|---|
Forgetting <div id="crovly-captcha"></div> | Add the container div inside your form |
| Using the site key instead of the API key for backend verification | Use crvl_secret_* for /verify-token |
Calling /verify-token from the browser | This endpoint is server-to-server only |
Not passing Content-Type: application/json | All POST endpoints require this header |
| Reusing a token | Each token can only be verified once |
| Hardcoding the token instead of reading from the form | Read crovly-token from form data |