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 ───────────────────────┤                     │
🔑
API Key is safe to include in browser JavaScript — it can only issue and solve challenges. Client Secret is used only in your backend — never expose it in the browser.
1

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:

CredentialWhere to use itSafe to expose?
API KeyBrowser JS (BasharYab.init)✓ Yes
Client IDYour backend verify callLow risk — keep server-side
Client SecretYour backend verify call✗ Never in browser
⚠️
The Client Secret is shown only once at creation. Store it in an environment variable — never commit it to source control.
2

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
Wrong answers are rejected server-side at /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,
      }),
    });
  }
});
3

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
⚠️
Each token is consumed on first use. A second verify call with the same token returns 404 — replay attacks are blocked by design.

API reference

MethodEndpointCallerAuthDescription
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

CredentialHeader / FieldUsed onExpose 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

EndpointLimit
/v1/captcha/issue60 req / min per IP · 6,000 req / hour per site
/v1/captcha/solve60 req / min per IP · 6,000 req / hour per site
/v1/captcha/verify120 req / min per site

Exceeded limits → HTTP 429 with Retry-After: <seconds> header.

Error codes

HTTPEndpointMeaning
401anyMissing or invalid API key / credentials
404/verifyToken not found or TTL expired (5 min)
409/verifyToken already used — replay blocked
422/solveIncorrect answer — no token issued
429anyRate limit or monthly quota exceeded