Crovly

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:

  1. Your site key is valid. Invalid site keys return a 403 without CORS headers.
  2. The domain matches. The site key is bound to the domain you registered in the dashboard. localhost is allowed for all site keys during development.
  3. You are not calling the API directly from the browser. The /verify-token endpoint 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:

  1. Script tag is present and points to https://get.crovly.com/widget.js.
  2. data-site-key is set on the script tag or the container div (either works).
  3. The container div exists with id="crovly-captcha" or class="crovly-captcha". The widget uses a MutationObserver, so the div can be added dynamically (React, Vue, WordPress AJAX, etc.).
  4. Ad blockers are not blocking the script. Some aggressive ad blockers flag captcha scripts.
  5. 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:

  1. The data-size="invisible" attribute is on the script tag, not the div.
  2. The #crovly-captcha div is still present in the DOM (it is required even in invisible mode).
  3. 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 onVerify before 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 onExpire callback 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:

  1. API key is correct. Use the API key (starts with crvl_secret_), not the site key.
  2. Token is fresh. Tokens expire after 5 minutes and can only be used once.
  3. 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.
  4. 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:

  1. Set data-api-url on the widget to point to your self-hosted API.
  2. Ensure Redis is accessible from the Crovly server. Nonce caching and rate limiting require Redis.
  3. Ensure PostgreSQL is accessible for storing verification logs and site data.
  4. Set all environment variables as documented in the Self-Hosting guide.
  5. Check the health endpoint at /api/health to verify the server is running.

Common Mistakes

MistakeFix
Forgetting <div id="crovly-captcha"></div>Add the container div inside your form
Using the site key instead of the API key for backend verificationUse crvl_secret_* for /verify-token
Calling /verify-token from the browserThis endpoint is server-to-server only
Not passing Content-Type: application/jsonAll POST endpoints require this header
Reusing a tokenEach token can only be verified once
Hardcoding the token instead of reading from the formRead crovly-token from form data

On this page