{"record":{"id":"44af4e64229ee85a","repo":"antiwork/gumroad","slug":"we-couldn-t-load-the-security-check-this-is-often","errorCode":null,"errorMessage":"We couldn't load the security check. This is often caused by an ad blocker or privacy extension — try disabling it for this page, using a private/incognito window, or switching networks, then try again.","messagePattern":"We couldn't load the security check\\. This is often caused by an ad blocker or privacy extension — try disabling it for this page, using a private/incognito window, or switching networks, then try again\\.","errorType":"exception","errorClass":"RecaptchaUnavailableError","httpStatus":null,"severity":"error","filePath":"app/javascript/components/useRecaptcha.tsx","lineNumber":145,"sourceCode":"        });\n      });\n\n    const initPromise = loadRecaptchaScript(CHALLENGE_SCRIPT_URL).then(initRecaptcha);\n    initPromiseRef.current = initPromise;\n    // Swallow the rejection here so a blocked script doesn't surface as an unhandled promise\n    // rejection — execute() re-checks the stored promise and reports the failure to the user.\n    initPromise.catch(() => {});\n  }, [siteKey, scoreBased]);\n\n  const execute = () => {\n    if (!siteKey) return Promise.reject(new RecaptchaCancelledError());\n\n    if (scoreBased) {\n      return loadRecaptchaScript(scoreScriptUrl(siteKey))\n        .catch(() => {\n          // The script failing to load is environmental (blocked by an extension or the\n          // network), not a user dismissal — surface it as such so the UI can show guidance.\n          throw new RecaptchaUnavailableError();\n        })\n        .then(\n          () =>\n            new Promise<string>((resolve, reject) => {\n              grecaptcha.enterprise.ready(() => {\n                grecaptcha.enterprise.execute(siteKey, { action }).then(resolve, () => {\n                  // Score keys never show a challenge, so there is nothing for the user to\n                  // dismiss — a token failure here is also environmental.\n                  reject(new RecaptchaUnavailableError());\n                });\n              });\n            }),\n        );\n    }\n\n    // Wait for initialization to finish before checking the widget: a submit that lands\n    // before the script has loaded and rendered the widget is a normal timing race, not a\n    // blocked CAPTCHA, so we must not report it as unavailable prematurely.","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/components/useRecaptcha.tsx#L127-L163","documentation":"RecaptchaUnavailableError thrown from useRecaptcha's score-based (invisible) branch when the Google reCAPTCHA Enterprise script cannot be loaded — loadRecaptchaScript(scoreScriptUrl(siteKey)) rejects — or when grecaptcha.enterprise.execute() fails to produce a token. The class itself carries no message; callers (Checkout PaymentForm, follow forms) render the shared RECAPTCHA_UNAVAILABLE_MESSAGE guidance when they catch it. It is deliberately distinct from RecaptchaCancelledError (user dismissed a challenge): this error is environmental — an extension or network is blocking Google.","triggerScenarios":"Loading https://www.google.com/recaptcha/enterprise.js?render=<siteKey> fails: an ad blocker/privacy extension blocks it, CSP script-src doesn't allow www.google.com, the network is offline or filters Google (e.g. mainland China), or enterprise.execute() rejects (site key not valid for the domain).","commonSituations":"Checkout or follow-button submissions failing for a subset of buyers running uBlock/Privacy Badger; local dev with restrictive CSP; staging domains not added to the reCAPTCHA key's allowed-domain list; corporate proxies blocking third-party scripts.","solutions":["Disable the ad blocker/privacy extension for the page or use a private/incognito window — exactly what the message suggests.","Verify the page CSP allows script-src https://www.google.com (plus frame-src/connect-src for the widget flow).","Confirm the reCAPTCHA Enterprise site key's allowed domains include the current domain.","Test from another network to rule out filtered connectivity to google.com."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"import { RecaptchaCancelledError, RecaptchaUnavailableError } from \"$app/components/useRecaptcha\";\n\nconst isRecaptchaUnavailable = (e: unknown): e is RecaptchaUnavailableError => e instanceof RecaptchaUnavailableError;\nconst isRecaptchaCancelled = (e: unknown): e is RecaptchaCancelledError => e instanceof RecaptchaCancelledError;","tryCatchPattern":"try {\n  const token = await execute();\n} catch (e) {\n  if (isRecaptchaCancelled(e)) return; // user dismissed; retry quietly\n  if (isRecaptchaUnavailable(e)) {\n    showAlert(RECAPTCHA_UNAVAILABLE_MESSAGE, \"error\"); // actionable guidance\n    return;\n  }\n  throw e;\n}","preventionTips":["Include www.google.com in script-src/frame-src CSP entries wherever the hook is used.","Start loading the reCAPTCHA script on page mount, not on submit, so load failures surface earlier.","Whitelist staging domains on the reCAPTCHA Enterprise key.","Preserve the Cancelled vs Unavailable distinction — never show blocker guidance for a user dismissal."],"tags":["recaptcha","captcha","browser-extensions","csp","checkout"],"backgroundTag":"recaptcha-load-failure","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}