Node.js tarafında Cloudflare Turnstile üç işleme iner: sayfadaki 0x ile başlayan sitekey'i çıkarın, method=turnstile ile CaptchaAI'ye gönderin, dönen token'ı formdaki cf-turnstile-response alanına yazıp gönderin. Bunun için headless tarayıcı çalıştırmanız gerekmez — Node.js 18'in yerleşik fetch'i ve iki HTTP çağrısı yeterlidir.
Aşağıdaki kodun tamamı üretimde kullanılan biçimiyle verilmiştir: önce üç adımı tek tek, sonra hepsini birleştiren yeniden kullanılabilir bir sınıf. CaptchaAI'nin Turnstile için yayımladığı çözüm süresi tavanı <10 saniye; başarı oranı da bu türde yüksek seyrediyor. Kodunuzdaki zaman aşımı değerlerini bu tavana göre ayarlayın.
Başlamadan önce ne gerekiyor?
- Node.js 18+ (yerel
fetchdesteği için) - CaptchaAI API anahtarı ve panelde bakiye
- Hedef sayfanın HTML'ine erişim (formun bulunduğu URL)
Hesabınız henüz yoksa panelden kayıt olup API anahtarınızı kopyalamanız yaklaşık beş dakika sürer; anahtar hazır olduğunda aşağıdaki kod doğrudan çalışır.
Adım 1: 0x ile başlayan sitekey'i sayfadan çıkarın
Turnstile sitekey'i her zaman 0x ön ekiyle başlar — bu, onu reCAPTCHA'nın 6Le ile başlayan anahtarından ayıran en pratik işarettir. Anahtar sayfaya dört farklı biçimde yerleşebilir, bu yüzden tek bir regex'e güvenmeyin: cf-turnstile sınıfına sahip div, herhangi bir elemandaki data-sitekey, turnstile.render çağrısı ve satır içi script'teki genel sitekey alanı sırayla denenmelidir.
async function extractTurnstileSitekey(url) {
const resp = await fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
},
});
const html = await resp.text();
// Method 1: data-sitekey attribute on Turnstile div
const divMatch = html.match(
/class=["'][^"]*cf-turnstile[^"]*["'][^>]*data-sitekey=["']([0-9x][A-Za-z0-9_-]+)["']/
);
if (divMatch) return divMatch[1];
// Method 2: data-sitekey on any element (Turnstile keys start with 0x)
const attrMatch = html.match(
/data-sitekey=["'](0x[A-Za-z0-9_-]+)["']/
);
if (attrMatch) return attrMatch[1];
// Method 3: In JavaScript turnstile.render call
const jsMatch = html.match(
/turnstile\.render\s*\([^,]+,\s*\{[^}]*sitekey\s*:\s*["']([0-9x][A-Za-z0-9_-]+)["']/
);
if (jsMatch) return jsMatch[1];
// Method 4: Generic sitekey in inline script
const inlineMatch = html.match(
/sitekey\s*:\s*["'](0x[A-Za-z0-9_-]+)["']/
);
if (inlineMatch) return inlineMatch[1];
return null;
}
Fonksiyon null dönerse anahtar büyük olasılıkla JavaScript ile sonradan enjekte ediliyordur; bu durumu aşağıdaki sorun giderme tablosunda ele alıyoruz.
Adım 2: Turnstile'ı CaptchaAI API'siyle çözün
Çözüm iki aşamalıdır: görevi in.php uç noktasına gönderin, ardından res.php üzerinden periyodik sorgulama yapın. json: "1" parametresini geçerek yanıtları düz metin yerine JSON olarak alırsınız; hata ayıklaması belirgin biçimde kolaylaşır.
const API_KEY = "YOUR_API_KEY";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function solveTurnstile(sitekey, pageurl, action = null) {
// Submit task
const submitData = {
key: API_KEY,
method: "turnstile",
sitekey: sitekey,
pageurl: pageurl,
json: "1",
};
if (action) {
submitData.action = action;
}
const submitResp = await fetch("https://ocr.captchaai.com/in.php", {
method: "POST",
body: new URLSearchParams(submitData),
});
const submitResult = await submitResp.json();
if (submitResult.status !== 1) {
throw new Error(`Submit error: ${submitResult.request}`);
}
const taskId = submitResult.request;
console.log(`Task ID: ${taskId}`);
// Poll for result
for (let i = 0; i < 30; i++) {
await sleep(5000);
const pollResp = await fetch(
`https://ocr.captchaai.com/res.php?${new URLSearchParams({
key: API_KEY,
action: "get",
id: taskId,
json: "1",
})}`
);
const pollResult = await pollResp.json();
if (pollResult.status === 1) {
return pollResult.request;
}
if (pollResult.request === "ERROR_CAPTCHA_UNSOLVABLE") {
throw new Error("Turnstile unsolvable");
}
}
throw new Error("Solve timed out");
}
Sorgulama aralığı olarak 5 saniye makul bir başlangıçtır. Daha kısa aralıklar gereksiz istek üretir, daha uzun aralıklar ise <10 saniyelik çözüm penceresini boşa harcar. CaptchaAI planları thread tabanlıdır: aynı anda kaç Turnstile görevinin havada olabileceğini plan belirler, çözüm başına ayrı ücret yoktur. BASIC ($15/ay, 5 thread) tek bir worker için fazlasıyla yeterlidir; paralel kuyruklar çalıştıran ekipler genellikle ADVANCE ($90/ay, 50 thread) seviyesinden başlar.
Adım 3: Token'ı cf-turnstile-response ile gönderin
Dönen token, formun gizli alanı olarak cf-turnstile-response adıyla gönderilir. Alan adını değiştirmeyin ve reCAPTCHA entegrasyonlarından alışkanlık haline gelmiş alan adını buraya taşımayın — yanlış alan adı, üretimde en sık görülen sessiz hatalardan biridir.
async function submitTurnstileForm(url, formData, token) {
const body = new URLSearchParams({
...formData,
"cf-turnstile-response": token,
});
const resp = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
},
body,
});
return {
status: resp.status,
body: await resp.text(),
};
}
Token'ın ömrü kısadır. Çözümü aldığınız an gönderim yapın; araya uzun iş kuyrukları veya yeniden deneme beklemeleri koymayın.
Üç adımı tek akışta birleştirmek
Aşağıdaki fonksiyon, sitekey çıkarma → çözme → gönderme sırasını uçtan uca çalıştırır ve QA ortamındaki bir giriş formunu örnek alır.
async function loginWithTurnstile(loginUrl, credentials) {
// Step 1: Extract sitekey
const sitekey = await extractTurnstileSitekey(loginUrl);
if (!sitekey) {
throw new Error("Turnstile sitekey not found");
}
console.log(`Sitekey: ${sitekey}`);
// Step 2: Solve Turnstile
const token = await solveTurnstile(sitekey, loginUrl);
console.log(`Token: ${token.substring(0, 50)}...`);
// Step 3: Submit form
const result = await submitTurnstileForm(loginUrl, credentials, token);
console.log(`Result: ${result.status}`);
return result;
}
// Usage
const result = await loginWithTurnstile("https://staging.example.com/qa-login", {
email: "user@example.com",
password: "pass123",
});
Üretim için yeniden kullanılabilir çözücü sınıfı
Tek seferlik bir script yerine sürekli çalışan bir servis yazıyorsanız, API anahtarını private alanda tutan ve tespit ile çözümü tek çağrıda birleştiren bir sınıf daha bakımı kolay bir yapı sunar.
class TurnstileSolver {
#apiKey;
constructor(apiKey) {
this.#apiKey = apiKey;
}
async solve(sitekey, pageurl, options = {}) {
const taskId = await this.#submit(sitekey, pageurl, options);
return await this.#poll(taskId);
}
async detectAndSolve(url) {
const sitekey = await this.#detect(url);
if (!sitekey) throw new Error("No Turnstile found");
return await this.solve(sitekey, url);
}
async #detect(url) {
const resp = await fetch(url, {
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0" },
});
const html = await resp.text();
const match = html.match(/data-sitekey=["'](0x[A-Za-z0-9_-]+)["']/);
return match ? match[1] : null;
}
async #submit(sitekey, pageurl, options) {
const body = new URLSearchParams({
key: this.#apiKey,
method: "turnstile",
sitekey,
pageurl,
json: "1",
...(options.action && { action: options.action }),
...(options.cdata && { data: options.cdata }),
});
const resp = await fetch("https://ocr.captchaai.com/in.php", {
method: "POST",
body,
});
const data = await resp.json();
if (data.status !== 1) throw new Error(`Submit: ${data.request}`);
return data.request;
}
async #poll(taskId) {
const params = new URLSearchParams({
key: this.#apiKey,
action: "get",
id: taskId,
json: "1",
});
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 5000));
const resp = await fetch(`https://ocr.captchaai.com/res.php?${params}`);
const data = await resp.json();
if (data.status === 1) return data.request;
if (data.request === "ERROR_CAPTCHA_UNSOLVABLE") {
throw new Error("Unsolvable");
}
}
throw new Error("Timed out");
}
}
// Usage
const solver = new TurnstileSolver("YOUR_API_KEY");
const token = await solver.detectAndSolve("https://staging.example.com/qa-login");
Bu sınıf, birden çok proje arasında paylaşılabilecek en küçük yararlı birimdir: detectAndSolve çağrısı hem sitekey'i bulur hem token'ı döndürür.
action ve cData parametreli uygulamalar
Bazı Turnstile kurulumları isteğe ek bağlam parametreleri ekler. Sayfada data-action veya JavaScript içinde action: görüyorsanız, aynı değeri çözüm isteğine de geçirmeniz gerekir; aksi halde dönen token doğrulama adımında reddedilir.
// Extract action from the page
function extractTurnstileAction(html) {
const match = html.match(
/data-action=["']([^"']+)["']|action\s*:\s*["']([^"']+)["']/
);
return match ? match[1] || match[2] : null;
}
// Solve with action
const token = await solver.solve(sitekey, pageurl, {
action: "login",
cdata: "session_abc123",
});
Token'ı kendi sunucunuzda doğrulama
Turnstile'ı yalnızca çözen tarafta değil, doğrulayan tarafta da kullanıyorsanız — örneğin kendi test ortamınızın form uç noktasını yazıyorsanız — Cloudflare'in siteverify uç noktası akışın diğer yarısını tamamlar.
async function verifyTurnstileToken(token, ip) {
const resp = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
secret: "YOUR_TURNSTILE_SECRET_KEY",
response: token,
remoteip: ip,
}),
}
);
const data = await resp.json();
return data.success;
}
Türkiye'deki ekipler için pratik bir senaryo
Yerel pazarın en yaygın kullanım biçimi e-ticaret ve fintech tarafındaki regresyon testleri: satın alma akışının kendi staging ortamınızda her gece uçtan uca çalıştığını doğrulamak. Turnstile bu akışın ortasında durduğunda gece koşusu sessizce düşer ve sabah ekip nedenini arar.
Pratik kurulum şöyle görünür: test koşucusu Europe/Istanbul saat dilimine göre 03:00'te tetiklenir, tr-TR locale'i ve +90 formatındaki sahte telefon numaralarıyla formu doldurur, Turnstile adımında yukarıdaki TurnstileSolver sınıfını çağırır ve token'ı forma yerleştirip akışa devam eder. Beş paralel senaryo çalıştıran bir ekip için BASIC planın 5 thread'i bu işi karşılar; senaryo sayısını artırdıkça thread sayısını yükseltmek yeterlidir, çünkü çözüm başına ek ücret yoktur.
İki noktayı hatırlatmakta fayda var. Birincisi, fiyatlar USD üzerindendir ve TL kurundaki oynaklık nedeniyle aylık maliyeti USD olarak planlamak daha öngörülebilirdir. İkincisi, test verisi üretirken gerçek müşteri verisi kullanmayın: KVKK kapsamındaki kişisel veriler yerine sentetik kayıtlarla çalışın ve otomasyonu yalnızca yetkiniz olan ortamlarda çalıştırın.
Sorun giderme
| Belirti | Olası neden | Çözüm |
|---|---|---|
Sitekey 6Le ile başlıyor |
Bu Turnstile değil, reCAPTCHA | method=userrecaptcha ile gönderin |
| Token reddedildi | Yanlış sitekey veya süresi dolmuş token | Sitekey'i yeniden çıkarın, token'ı hemen gönderin |
| Sitekey bulunamadı | Turnstile JavaScript ile sonradan yükleniyor | Sayfayı Puppeteer veya Playwright ile render edin |
ERROR_BAD_PARAMETERS |
Eksik sitekey veya pageurl |
İki parametrenin de dolu gittiğini doğrulayın |
| Gönderimden sonra 403 | İsteğin başlıkları eksik | Gerçekçi bir User-Agent ve Content-Type gönderin |
| Sorgulama zaman aşımına uğruyor | Kuyruk dolu ya da thread sayısı yetersiz | Eşzamanlı görev sayısını düşürün veya planı yükseltin |
Sık sorulan sorular
Turnstile çözmek için headless tarayıcı çalıştırmam şart mı?
Hayır. Yukarıdaki akışın tamamı düz fetch istekleriyle çalışır. Tarayıcıya yalnızca sitekey sayfaya JavaScript ile enjekte ediliyorsa ve HTML içinde görünmüyorsa ihtiyaç duyarsınız; o durumda da tarayıcıyı sadece HTML'i render etmek için kullanır, çözümü yine API'ye bırakırsınız.
Aldığım token ne kadar süre geçerli kalır?
Turnstile token'ları kısa ömürlüdür ve tek kullanımlıktır. Token'ı önbelleğe almayın, yeniden kullanmayın; çözüm ile form gönderimi arasındaki süreyi mümkün olduğunca kısa tutun.
Aynı anda kaç Turnstile görevi çalıştırabilirim?
Plan başına ayrılan thread sayısı kadar. Her thread havadaki bir çözümü temsil eder ve iş bitince yeni görevi almak üzere serbest kalır; çözüm başına ek ücret yoktur. BASIC ($15/ay, 5 thread) küçük koşular için, STANDARD ($30/ay, 15 thread) ve üzeri paralel test kuyrukları için uygundur.
Aynı kodla hCaptcha da çözebilir miyim?
Hayır. CaptchaAI hCaptcha'yı desteklemiyor. Bu makaledeki akış Cloudflare Turnstile ve method=turnstile içindir; reCAPTCHA v2/v3, Cloudflare doğrulama akışı, GeeTest v3 ve görüntü tabanlı türler ayrı method değerleriyle çözülür.
Özet
Node.js tarafında Turnstile için ezberlenecek üç şey var: sitekey 0x ile başlar, çözüm isteği method=turnstile kullanır, token cf-turnstile-response alanıyla gönderilir. Geri kalanı standart HTTP işidir. Hesap açma ve API anahtarı için CaptchaAI panelinden başlayabilirsiniz.