Crovly

Desktop Apps

Add Crovly captcha to desktop apps — Electron, Tauri, .NET WPF, Qt, and JavaFX using embedded WebView.

How It Works

Desktop apps don't run in a browser, but they can embed a WebView to load the Crovly widget. The flow is identical to web:

  1. WebView loads a small HTML page containing widget.js
  2. User completes the captcha inside the WebView
  3. Your app receives the token via postMessage or a JS bridge
  4. Your backend verifies the token with your secret key

No special SDK is needed. Any platform with a WebView can integrate Crovly in a few lines of code.

Captcha HTML

Save this as a local file or serve it from your server. The WebView will load it:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <script src="https://get.crovly.com/widget.js"
          data-site-key="YOUR_SITE_KEY"
          data-theme="auto"></script>
  <style>
    body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: transparent; }
  </style>
</head>
<body>
  <div id="crovly-captcha" data-callback="onVerify"></div>
  <script>
    function onVerify(token) {
      // Send token to the host app
      window.postMessage({ type: 'crovly-token', token }, '*');
    }
  </script>
</body>
</html>

Electron

const { BrowserWindow, ipcMain } = require('electron');

const captchaWin = new BrowserWindow({
  width: 350,
  height: 200,
  webPreferences: { preload: __dirname + '/preload.js' },
});

captchaWin.loadFile('captcha.html');

// preload.js
const { ipcRenderer } = require('electron');
window.addEventListener('message', (e) => {
  if (e.data?.type === 'crovly-token') {
    ipcRenderer.send('crovly-token', e.data.token);
  }
});

// main process
ipcMain.on('crovly-token', (event, token) => {
  // Send token to your backend for verification
  verifyToken(token);
});

Tauri (Rust + JS)

use tauri::Manager;

#[tauri::command]
fn handle_captcha_token(token: String) {
    // Send token to your backend for verification
    println!("Token: {}", token);
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![handle_captcha_token])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
// In your Tauri frontend
const { invoke } = window.__TAURI__.core;

function onVerify(token) {
  invoke('handle_captcha_token', { token });
}

.NET (WPF / WinForms)

WPF with WebView2

<Window xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf">
    <wv2:WebView2 Name="CaptchaWebView" Source="captcha.html" />
</Window>
CaptchaWebView.WebMessageReceived += (sender, args) =>
{
    var message = JsonSerializer.Deserialize<JsonElement>(args.WebMessageAsJson);
    if (message.GetProperty("type").GetString() == "crovly-token")
    {
        string token = message.GetProperty("token").GetString();
        // Send token to your backend for verification
    }
};

WinForms with WebView2

var webView = new Microsoft.Web.WebView2.WinForms.WebView2();
webView.Source = new Uri("file:///path/to/captcha.html");
webView.WebMessageReceived += (s, e) =>
{
    var data = JsonDocument.Parse(e.WebMessageAsJson).RootElement;
    if (data.GetProperty("type").GetString() == "crovly-token")
    {
        string token = data.GetProperty("token").GetString();
        VerifyToken(token);
    }
};

Qt (C++)

#include <QWebEngineView>
#include <QWebChannel>

QWebEngineView *view = new QWebEngineView();
view->load(QUrl("qrc:/captcha.html"));

// Use QWebChannel to receive the token
QWebChannel *channel = new QWebChannel(view->page());
view->page()->setWebChannel(channel);

// In JavaScript: new QWebChannel(qt.webChannelTransport, function(channel) { ... })

Java (JavaFX)

import javafx.scene.web.WebView;
import javafx.scene.web.WebEngine;

WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.load("file:///path/to/captcha.html");

// Listen for token via title change (simple bridge)
engine.titleProperty().addListener((obs, oldTitle, newTitle) -> {
    if (newTitle != null && newTitle.startsWith("crovly-token:")) {
        String token = newTitle.substring("crovly-token:".length());
        verifyToken(token);
    }
});

macOS (Swift)

Use the same approach as the iOS SDKWKWebView works on macOS too:

import WebKit

let webView = WKWebView(frame: .zero)
webView.loadFileURL(htmlURL, allowingReadAccessTo: htmlURL.deletingLastPathComponent())

// Use WKScriptMessageHandler to receive the token
// See the iOS SDK docs for the full implementation

Backend Verification

After receiving the token from the WebView, verify it on your backend:

const res = await fetch('https://api.crovly.com/verify-token', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_SECRET_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ token, expectedIp: clientIp }),
});

const { success, score } = await res.json();

See the Node.js, PHP, or Python SDK docs for backend libraries.

Tips

  • Theme: Use data-theme="auto" to match the OS light/dark mode
  • Size: Set the WebView to approximately 300×80px for the best fit
  • Offline: The widget requires internet access to fetch challenges
  • Testing: Use test_always_pass as the site key during development

On this page