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:
- WebView loads a small HTML page containing
widget.js - User completes the captcha inside the WebView
- Your app receives the token via
postMessageor a JS bridge - 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 SDK — WKWebView 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 implementationBackend 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_passas the site key during development