Integration Guide
Add CAPTCHA protection to any page in three steps.
How it works
Basharyab verifies the user's answer in the browser before your backend ever sees it.
Your server only receives a short-lived verify_token — the raw answer never leaves the browser.
Browser Your Backend Basharyab
│ │ │
├─ POST /v1/captcha/issue ─────────────────────────────►│ (X-API-Key)
│◄─ { challenge_id, image } ────────────────────────────┤
│ │ │
│ [user solves the challenge] │ │
│ │ │
├─ POST /v1/captcha/solve ─────────────────────────────►│ (X-API-Key)
│ { challenge_id, solution } │ │
│◄─ { verify_token } ──────────────────────────────────┤ 5-min TTL
│ │ │
│ [widget injects hidden field] │ │
├─ POST /your-form ───────────────►│ │
│ captcha_verify_token │ │
│ ├─ POST /v1/captcha/verify ──►│
│ │ { client_id, │
│ │ verify_token } │
│ │◄─ { valid: true } ──┤
│◄─ success ───────────────────────┤ │
Create an account & get credentials
Register, create a Workspace, then add a Project. Projects are active immediately — no domain verification required. You get three credentials:
| Credential | Where to use it | Safe to expose? |
|---|---|---|
API Key | Browser JS (BasharYab.init) | ✓ Yes |
Client ID | Your backend verify call | Low risk — keep server-side |
Client Secret | Your backend verify call | ✗ Never in browser |
Add the widget to your page
Place the widget anywhere on the page — it does not need to be inside the form.
When the user solves the CAPTCHA, the widget automatically calls /v1/captcha/solve,
gets a verify_token, and injects it as a hidden field into your form.
<!-- The widget — can be anywhere on the page -->
<div id="basharyab-captcha"></div>
<form id="main-form" method="POST" action="/submit">
<input type="text" name="email" placeholder="Email">
<!-- captcha_verify_token is injected here automatically -->
<button type="submit">Submit</button>
</form>
<script src="/static/js/captcha-client.js"></script>
<script>
BasharYab.init({
container: '#basharyab-captcha',
apiKey: 'YOUR_API_KEY',
tokenForm: '#main-form' // widget injects captcha_verify_token into this form
});
</script>
Once solved, the widget injects this into the target form:
<input type="hidden" name="captcha_verify_token" value="a1b2c3d4-e5f6-...">
Your backend receives a regular form POST:
POST /submit
Content-Type: application/x-www-form-urlencoded
email=user%40example.com&captcha_verify_token=a1b2c3d4-e5f6-7890-abcd-ef1234567890
/solve with 422. A token is only issued when the answer is correct. The raw answer never reaches your backend.AJAX / SPA — use onSolve callback instead:
BasharYab.init({
container: '#basharyab-captcha',
apiKey: 'YOUR_API_KEY',
onSolve: function (data) {
// data.verifyToken — send this to your backend
fetch('/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: document.getElementById('email').value,
captcha_verify_token: data.verifyToken,
}),
});
}
});
Verify on your server
When your backend receives the form, call POST /v1/captcha/verify with the token
before processing the request. Never call this endpoint from the browser.
func verifyCaptcha(r *http.Request) (bool, error) {
token := r.FormValue("captcha_verify_token")
if token == "" {
return false, nil
}
body, _ := json.Marshal(map[string]string{
"client_id": "YOUR_CLIENT_ID",
"verify_token": token,
})
req, _ := http.NewRequest("POST", "https://basharyab.com/v1/captcha/verify",
bytes.NewReader(body))
req.Header.Set("X-Client-Secret", "YOUR_CLIENT_SECRET")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, fmt.Errorf("captcha verify: %w", err)
}
defer resp.Body.Close()
var result struct{ Valid bool `json:"valid"` }
_ = json.NewDecoder(resp.Body).Decode(&result)
return result.Valid, nil
}
<?php
$token = $_POST['captcha_verify_token'] ?? '';
if (empty($token)) {
http_response_code(400);
exit('CAPTCHA required');
}
$ch = curl_init('https://basharyab.com/v1/captcha/verify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Client-Secret: YOUR_CLIENT_SECRET',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'client_id' => 'YOUR_CLIENT_ID',
'verify_token' => $token,
]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($result['valid'])) {
http_response_code(400);
exit('CAPTCHA verification failed');
}
import requests
def verify_captcha(form_data: dict) -> bool:
token = form_data.get("captcha_verify_token", "")
if not token:
return False
resp = requests.post(
"https://basharyab.com/v1/captcha/verify",
headers={"X-Client-Secret": "YOUR_CLIENT_SECRET"},
json={
"client_id": "YOUR_CLIENT_ID",
"verify_token": token,
},
timeout=5,
)
return resp.json().get("valid", False)
curl -X POST https://basharyab.com/v1/captcha/verify \
-H "X-Client-Secret: YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"verify_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}'
Response
// Success
{ "success": true, "valid": true }
// Token not found or expired (5-min TTL)
{ "error": "verify token not found or expired" } // 404
// Token already used — replay blocked
{ "error": "challenge already used" } // 409
// Wrong credentials
{ "error": "invalid credentials" } // 401
404 — replay attacks are blocked by design.API reference
| Method | Endpoint | Caller | Auth | Description |
|---|---|---|---|---|
| GET | /v1/health |
Anyone | — | Liveness check |
| POST | /v1/captcha/issue |
Browser | X-API-Key |
Issue a challenge — returns image + challenge_id |
| POST | /v1/captcha/solve |
Browser | X-API-Key |
Verify user's answer → returns verify_token (5-min TTL) |
| POST | /v1/captcha/verify |
Backend only | X-Client-Secret |
Confirm verify_token — returns { valid: true } |
Credentials
| Credential | Header / Field | Used on | Expose in browser? |
|---|---|---|---|
| API Key | X-API-Key |
/issue · /solve |
✓ Yes |
| Client ID | JSON body client_id |
/verify |
Low risk — keep server-side |
| Client Secret | X-Client-Secret |
/verify |
✗ Never |
Rate limits
| Endpoint | Limit |
|---|---|
/v1/captcha/issue | 60 req / min per IP · 6,000 req / hour per site |
/v1/captcha/solve | 60 req / min per IP · 6,000 req / hour per site |
/v1/captcha/verify | 120 req / min per site |
Exceeded limits → HTTP 429 with Retry-After: <seconds> header.
Error codes
| HTTP | Endpoint | Meaning |
|---|---|---|
401 | any | Missing or invalid API key / credentials |
404 | /verify | Token not found or TTL expired (5 min) |
409 | /verify | Token already used — replay blocked |
422 | /solve | Incorrect answer — no token issued |
429 | any | Rate limit or monthly quota exceeded |