Anti-bot verification API · v1
CoinGuard manush ar bot alada kore. Ekta chhobi te koyekta number thake — user chhoto theke boro tap kore. Shothik uttor kokhono browser e jay na, tai bot chhobi na poRe pass korte pare na.
Duita call. Duitai tomar server theke, browser theke na.
Shobcheye boro niyom: verify call tomar server theke hote hobe, secret key diye. Browser theke korle bot nijei call kore result upekkha korbe — pura system ortho-hin hoye jabe.
Protita site er duita key thake. Ei page e asol key dekhano hoy na — Panel → Sites theke nao.
site key
browser e jete pare
cg_site_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
secret key
shudhu server er .env e — kokhono browser e na
cg_secret_9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a3928
Notun key banate: Panel → Sites → + New site. Secret ek baroi dekhabe — sathe sathe kopi koro.
Secret harale ba faas hole: Panel → Sites → Rotate secret. Purono ta sathe sathe ojoggo hoye jabe.
Client project er .env file er shesh e jog koro:
CG_URL=https://samzune.com
CG_SITE=cg_site_...
CG_SECRET=cg_secret_...
Tarpor config/services.php er array te jog koro:
'coinguard' => [
'url' => env('CG_URL'),
'site' => env('CG_SITE'),
'secret' => env('CG_SECRET'),
],
Sheshe: php artisan config:clear
Ei file ta banao —
app/Services/CoinGuard.php.
Eta poroborti shob step e byabohar hobe.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class CoinGuard
{
private string $url;
private string $site;
private string $secret;
public function __construct()
{
$this->url = rtrim((string) config('services.coinguard.url'), '/');
$this->site = (string) config('services.coinguard.site');
$this->secret = (string) config('services.coinguard.secret');
}
/**
* Notun challenge ano - page load er shomoy dako.
* Ferot dey ['id' => ..., 'image' => ..., 'taps' => ...] ba null.
*/
public function challenge(): ?array
{
$r = request();
try {
$res = Http::asForm()->timeout(10)->post($this->url . '/cg/challenge', [
'site' => $this->site,
'ua' => (string) $r->userAgent(),
'ip' => (string) $r->ip(),
'host' => (string) $r->getHost(),
]);
} catch (\Throwable $e) {
report($e);
return null;
}
$j = $res->json();
if (!is_array($j) || empty($j['success'])) {
return null;
}
return [
'id' => (string) $j['id'],
'image' => (string) $j['image'],
'taps' => (int) $j['taps'],
];
}
/**
* Form submit er por dako. true hole manush.
* $id = hidden field cg_id
* $taps = hidden field cg_taps
*/
public function verify(?string $id, ?string $taps): bool
{
if (empty($id) || empty($taps)) {
return false;
}
$r = request();
try {
$res = Http::asForm()->timeout(10)->post($this->url . '/cg/verify', [
'site' => $this->site,
'secret' => $this->secret,
'id' => $id,
'taps' => $taps,
'ua' => (string) $r->userAgent(),
'ip' => (string) $r->ip(),
]);
} catch (\Throwable $e) {
report($e);
return false;
}
$j = $res->json();
return is_array($j) && !empty($j['success']);
}
}
Khyal koro: network fail hole eta false dey —
mane reward dey na. Eta icchakrito: sondeho hole nirapod dike bhanga bhalo.
Duijaygay: page dekhanor shomoy challenge ano, form ashar por verify koro.
<?php
namespace App\Http\Controllers;
use App\Services\CoinGuard;
use Illuminate\Http\Request;
class ClaimController extends Controller
{
// 1) page dekhai - challenge ene view e pathai
public function show(CoinGuard $cg)
{
return view('claim', [
'cg' => $cg->challenge(), // null hole niche dekhano ache ki korte hobe
]);
}
// 2) form ashe - agey verify, tarpor reward
public function store(Request $r, CoinGuard $cg)
{
if (!$cg->verify($r->input('cg_id'), $r->input('cg_taps'))) {
return back()->withErrors([
'cg' => 'Verification hoy nai. Abar chesta koro.',
]);
}
// ---- ekhan theke tomar nijer logic ----
// timer check, daily limit, tarpor reward
// $user->increment('balance', 10);
return back()->with('ok', 'Reward peye gecho!');
}
}
Route duita:
Route::get('/claim', [ClaimController::class, 'show']);
Route::post('/claim', [ClaimController::class, 'store']);
Bhul kora jabe na: verify er por reward — ulto na. Ar reward er logic
verify() er bhitore na, oi if block er pore.
resources/views/claim.blade.php —
chhobi dekhay, tap dhore, hidden field e bhore.
<form method="POST" action="/claim">
@csrf
@if ($cg)
<p>Chhoto theke boro tap koro</p>
<div id="cgWrap" style="position:relative;width:320px;max-width:100%;
border-radius:14px;overflow:hidden;cursor:pointer;touch-action:manipulation">
<img src="{{ $cg['image'] }}" alt="" draggable="false"
style="display:block;width:100%;height:auto;pointer-events:none">
</div>
<div style="margin-top:10px">
<span id="cgCount">0 / {{ $cg['taps'] }}</span>
<span id="cgUndo" style="cursor:pointer;margin-left:12px">undo</span>
</div>
<input type="hidden" name="cg_id" value="{{ $cg['id'] }}">
<input type="hidden" name="cg_taps" id="cgTaps" value="">
<button type="submit">Claim</button>
@else
<p>Verification service ekhon paoa jacche na. Page refresh koro.</p>
@endif
</form>
@if ($cg)
<script>
(function () {
var need = {{ $cg['taps'] }};
var wrap = document.getElementById('cgWrap');
var f = document.getElementById('cgTaps');
var c = document.getElementById('cgCount');
var taps = [];
function paint() {
f.value = taps.join('|');
c.textContent = taps.length + ' / ' + need;
wrap.querySelectorAll('.cgdot').forEach(function (d) { d.remove() });
var r = wrap.getBoundingClientRect();
taps.forEach(function (t, i) {
var p = t.split(','), d = document.createElement('span');
d.className = 'cgdot';
d.textContent = i + 1;
d.style.cssText = 'position:absolute;width:26px;height:26px;border-radius:99px;' +
'background:#10b981;color:#04231a;font:800 13px sans-serif;display:grid;' +
'place-items:center;transform:translate(-50%,-50%);pointer-events:none;' +
'left:' + (p[0] * r.width / 320) + 'px;top:' + (p[1] * r.height / 200) + 'px';
wrap.appendChild(d);
});
}
wrap.addEventListener('click', function (e) {
if (taps.length >= need) return;
var r = wrap.getBoundingClientRect();
var x = Math.round((e.clientX - r.left) * 320 / r.width);
var y = Math.round((e.clientY - r.top) * 200 / r.height);
if (x < 0 || y < 0 || x > 320 || y > 200) return;
taps.push(x + ',' + y);
paint();
});
document.getElementById('cgUndo').addEventListener('click', function () {
taps.pop(); paint();
});
wrap.closest('form').addEventListener('submit', function (e) {
if (taps.length !== need) { e.preventDefault(); c.textContent = 'shob tap koro' }
});
})();
</script>
@endif
Form jodi sadharon HTML form na hoy — ei ongsho ta MUST poRo.
Sadharon <form> submit korle hidden input er
man APNAAPNI server e chole jay. Kintu
fetch(), axios,
Alpine, Livewire, Vue, React — ei shob e jay NA.
JS ke hate hate cg_id ar cg_taps request body te
dite hobe. Na dile server e khali pouchabe, verify shathe shathe false debe,
ar CoinGuard e kono call e jabe na.
// ===== 1) Tap widget - tap gulo ekta global jaygay rakho =====
function cgTap(need, id) {
return {
need: need, cgid: id, taps: [],
get done() { return this.taps.length === this.need },
hit(e) {
if (this.taps.length >= this.need) return;
var r = this.$refs.wrap.getBoundingClientRect();
// JORURI: 320x200 scale e rupantor - na korle shob tap bhul hobe
var x = Math.round((e.clientX - r.left) * 320 / r.width);
var y = Math.round((e.clientY - r.top) * 200 / r.height);
if (x < 0 || y < 0 || x > 320 || y > 200) return;
this.taps.push(x + ',' + y);
this.sync();
},
undo() { this.taps.pop(); this.sync() },
clear() { this.taps = []; this.sync() },
// baire er submit function ei ta poRbe
sync() {
window.__cg = {
id: this.cgid,
taps: this.taps.join('|'),
done: this.taps.length === this.need,
};
},
init() { this.sync() },
}
}
// ===== 2) Submit korar shomoy - EI DUITA LINE BHULO NA =====
async function submitClaim() {
var cg = window.__cg || {}; // <-- ekhane poRchi
var res = await fetch('/claim', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
},
body: JSON.stringify({
// ... tomar nijer field gulo ...
cg_id: cg.id || '', // <-- EITA
cg_taps: cg.taps || '', // <-- AR EITA
}),
});
var d = await res.json();
}
Panel → API logs e jao. Jodi dekho
challenge → ok ache kintu
verify er ekta o line nei —
tahole nishchit, JS cg_id/cg_taps pathacche na. Onno kono karon e eta hoy na,
karon bhul uttor pathaleo ontoto ekta fail log thakto.
Captcha kaj korleo, user jodi na bojhe ki korte hobe, tahole se claim korte parbe na. Niche ja ja thakle user atkabe na.
× Undo ke shudhu text banabe na — button er moto dekhate hobe
× Chhobi te fixed width (320px) dibe na — chhoto phone e kete jabe.
width:100%; max-width:340px dao
× touch-action:manipulation bad dibe na —
na hole mobile e 300ms deri hobe
× Chhobi te pointer-events:none dite bhulbe na —
na hole click er hisheb golmal hobe
<!-- nirdesh + gona : chhobir UPORE -->
<div class="flex items-center mb-2.5">
<span class="text-[13.5px] font-bold">Tap the numbers, low to high</span>
<span x-show="!done" class="ml-auto text-[11.5px] font-bold tabular-nums opacity-40"
x-text="taps.length + ' / ' + need"></span>
<span x-show="done" x-cloak
class="ml-auto text-[11.5px] font-extrabold text-emerald-500">ready</span>
</div>
<!-- chhobi -->
<div x-ref="wrap" @click="hit($event)"
class="relative w-full rounded-2xl overflow-hidden cursor-pointer select-none border"
style="touch-action:manipulation;max-width:340px">
<img src="{{ $cg['image'] }}" alt="" draggable="false"
class="block w-full h-auto pointer-events-none">
<template x-for="(t, i) in taps" :key="i">
<span class="absolute grid place-items-center rounded-full font-extrabold pointer-events-none"
:style="dot(t)" x-text="i + 1"></span>
</template>
</div>
<!-- button gulo : chhobir NICHE -->
<div class="flex items-center gap-2 mt-3" style="max-width:340px">
<button type="button" @click="undo()" x-show="taps.length" x-cloak
class="inline-flex items-center gap-1.5 rounded-xl px-3 py-2 text-[12.5px]
font-bold bg-slate-100 dark:bg-white/[.07] border
border-slate-200 dark:border-white/10">
<svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="currentColor"
stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 010 10h-4"/>
</svg>
Undo
</button>
<button type="button" @click="clear()" x-show="taps.length > 1" x-cloak
class="rounded-xl px-3 py-2 text-[12.5px] font-bold opacity-50">Clear all</button>
<span x-show="!done" x-cloak class="ml-auto text-[11.5px] font-semibold opacity-40">
<span x-show="!taps.length">tap the smallest number first</span>
<span x-show="taps.length" x-text="(need - taps.length) + ' more'"></span>
</span>
<span x-show="done" x-cloak
class="ml-auto inline-flex items-center gap-1 text-[11.5px] font-extrabold text-emerald-500">
<svg viewBox="0 0 24 24" class="w-3.5 h-3.5" fill="none" stroke="currentColor"
stroke-width="3" stroke-linecap="round"><path d="M4 12l5 5L20 6"/></svg>
done
</span>
</div>
<!-- dot er style : percent diye, tai chhobi chhoto-boro hole-o thik jaygay boshe -->
<script>
function dot(t) {
var p = t.split(',');
return 'width:28px;height:28px;font-size:13.5px;'
+ 'background:linear-gradient(150deg,#34d399,#059669);color:#04231a;'
+ 'border:2px solid rgba(255,255,255,.85);'
+ 'box-shadow:0 4px 12px rgba(0,0,0,.45);'
+ 'transform:translate(-50%,-50%);'
+ 'left:' + (p[0] / 320 * 100) + '%;'
+ 'top:' + (p[1] / 200 * 100) + '%';
}
</script>
Duita endpoint. Duitai POST,
form-encoded body. Uttor shob shomoy JSON.
https://samzune.com/cg/challenge
Notun challenge ano
site
ha
Tomar site key
ua
ha
User er browser er User-Agent
ip
ha
User er IP
host
na
Tomar site er domain — domain lock kora thakle lagbe
{
"success": true,
"id": "5297f09ca6eb2113...",
"image": "data:image/png;base64,...",
"taps": 5,
"expires": 900
}
https://samzune.com/cg/verify
Uttor jachai koro
site
ha
Tomar site key
secret
ha
Secret key — shudhu server theke
id
ha
challenge theke paoa id
taps
ha
Coordinate — "60,75|39,159|..."
ua
ha
Ekoi user er UA (challenge er shomoy jeta chhilo)
ip
ha
Ekoi user er IP
{ "success": true }
// ba
{ "success": false, "error": "wrong-order" }
ua ar ip duijaygay ek hote hobe. challenge er shomoy je UA/IP pathabe,
verify tei ekoi ta pathate hobe — na hole
fp-mismatch ashbe. Ei chek tai id churi kore
onno jayga theke jachai kora bondho kore.
success:false holе
error field e karon thake.
bad-site-key
Site key bhul, ba site bondho kora ache
bad-secret
Secret key mile nai — .env dekho
bad-domain
Site e domain lock ache kintu host mile nai — panel e domain thik koro
rate-limit
Ei site er prot i minute er limit shesh — panel e Rate/min baRao
missing-field
Kono ekta field pathao nai
no-challenge
id nei — meyad shesh, ba agei byabohar hoyeche
expired
15 minute er beshi purono challenge
wrong-order
Tap er krom ba jayga bhul — manush bhul korle eta ashe
count
Joto tap dorkar tar cheye kom ba beshi
too-fast
Onek druto uttor — prai shomoy bot
fp-mismatch
UA ba IP mile nai — id churi ba bhul kore pathano
too-many
Ekoi challenge e 5 baro cheye beshi bhul
honeypot
Lukano field bhora hoyeche — manush kore na
Code na bujhleo shomossa nei. Niche Copy chapo, tarpor ChatGPT / Claude / Cursor — jekhane khushi paste koro. Sathe tomar site key ar secret key ta likhe dio.
Ami amar Laravel project e CoinGuard nam er ekta anti-bot captcha bosate chai.
Tumi amake step by step bolo ar puro code likhe dao.
=== CoinGuard kibhabe kaj kore ===
Ekta chhobi te koyekta number thake elomelo jaygay. User chhoto theke boro
krome tap kore. Shothik uttor kokhono browser e jay na - shudhu CoinGuard er
server e thake. Tai bot chhobi na poRe pass korte pare na.
=== Duita API endpoint ===
1) POST https://samzune.com/cg/challenge
Body (form-encoded): site, ua, ip, host
host = tomar site er domain (request()->getHost())
Uttor: {"success":true,"id":"...","image":"data:image/png;base64,...","taps":5}
2) POST https://samzune.com/cg/verify
Body (form-encoded): site, secret, id, taps, ua, ip
taps format: "60,75|39,159|214,166|151,100|241,83"
Uttor: {"success":true} othoba {"success":false,"error":"wrong-order"}
=== JORURI NIYOM ===
- Duita call e Laravel er SERVER theke korte hobe (Http::asForm()->post),
browser theke NA. Secret key kokhono browser e ba JS e pathabe na.
- challenge ar verify - duijaygay EKOI ua ar ip pathate hobe
(request()->userAgent() ar request()->ip()). Na hole fp-mismatch error ashbe.
- Browser shudhu tap er coordinate jomay: "x,y|x,y|..."
Coordinate 320x200 scale e pathate hobe. Chhobi CSS e chhoto-boro hole
bhag kore nite hobe: Math.round((e.clientX - rect.left) * 320 / rect.width)
- challenge call e host o pathate hobe (request()->getHost()) - panel e
domain lock kora thakle eta na pathale bad-domain error ashbe.
- verify() true hole tar POREI reward dite hobe - agey na.
- Ekta challenge ek baroi kaj kore. Fail hole notun challenge lagbe (page reload).
- Network fail hole verify false dey - mane reward dey na. Eta thik ache.
=== Amar key ===
CG_URL=https://samzune.com
CG_SITE=<ekhane amar site key bosao>
CG_SECRET=<ekhane amar secret key bosao>
=== Ja ja banate hobe ===
1. .env e uporer 3 ta line, ar config/services.php e 'coinguard' array
2. app/Services/CoinGuard.php - challenge() ar verify($id, $taps) method soho
3. Controller: show() e challenge ano, store() e verify koro tarpor reward
4. Blade: chhobi dekhao, tap dhoro, hidden field cg_id ar cg_taps e bhoro,
undo button, ar shob tap na hole submit atkao
5. Vanilla JS - jQuery chhara
Amar project e ekhon je faucet/claim page ta ache, sheta amake dekhao kothay
ki bosate hobe. Kono line mucho na jodi na bujho seta ki kore.
Sabdhan: prompt e secret key bosanor por sheta public jaygay (github, forum, screenshot) share korbe na. AI chat privateই rakho. Bhul kore faas hole panel theke Rotate secret koro.
CoinGuard · https://samzune.com