Integration Guide

Add CAPTCHA protection to any page in three steps.

How it works

The widget issues a challenge and lets the user solve it in the browser. Once solved, it gives you a challengeId and solution. Your backend sends those to BasharYab to confirm — the widget never contacts your backend directly.

Browser                          Your Backend        BasharYab
  │                                    │                  │
  ├─ POST /v1/captcha/issue ──────────────────────────►  │  X-API-Key
  │◄─ { challenge_id, payload } ────────────────────── ──┤
  │                                    │                  │
  │  [user solves the challenge]       │                  │
  │                                    │                  │
  │  onSuccess(challengeId, solution)  │                  │
  │                                    │                  │
  │  ── Form mode ──────────────────►  │                  │
  │    hidden inputs auto-injected     │                  │
  │                                    │                  │
  │  ── Callback mode ───────────────► │                  │
  │    your code sends via fetch/XHR   │                  │
  │                                    │                  │
  │                                    ├─ POST /v1/captcha/verify ──►│
  │                                    │  { client_id,    │
  │                                    │    challenge_id, │
  │                                    │    solution }    │
  │                                    │◄─ { valid: true }┤
  │◄─ success ─────────────────────────┤                  │
🔑
API Key is safe in browser JavaScript — it can only issue 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

Load captcha-widget.js from our server and set window.CAPTCHA_CONFIG before the script runs. The widget supports all provider types (proof-of-work, image-click, slide, rotate, and text). Pick the integration mode that fits your page.

Mode A — Form (auto-inject)

Place the widget container inside your <form>. On success the widget automatically injects two hidden inputs: captcha_challenge_id and captcha_solution. Your form POST carries them to your backend with zero extra code.

<form method="POST" action="/submit">
  <input type="text" name="email" placeholder="Email">

  <!-- Widget lives inside the form — hidden inputs are injected here -->
  <div id="my-captcha"></div>

  <button type="submit">Submit</button>
</form>

<script src="https://basharyab.com/js/captcha-widget.js"></script>
<script>
  window.CAPTCHA_CONFIG = {
    apiBase:   'https://basharyab.com',
    apiKey:    'YOUR_API_KEY',
    container: '#my-captcha'
    // no onSuccess needed — hidden inputs injected automatically
  };
</script>

Your backend receives a standard form POST:

POST /submit
Content-Type: application/x-www-form-urlencoded

email=user%40example.com
&captcha_challenge_id=550e8400-e29b-41d4-a716-446655440000
&captcha_solution=abc123

Mode B — Callback (AJAX / SPA)

Use onSuccess(challengeId, solution) when the widget is outside a form, or when you submit via fetch / XHR. The callback receives the challengeId and solution — send both to your backend.

<!-- Widget can be anywhere — does NOT need to be inside a form -->
<div id="my-captcha"></div>

<script src="https://basharyab.com/js/captcha-widget.js"></script>
<script>
  window.CAPTCHA_CONFIG = {
    apiBase:   'https://basharyab.com',
    apiKey:    'YOUR_API_KEY',
    container: '#my-captcha',
    onSuccess: function (challengeId, solution) {
      fetch('/submit', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email:                document.getElementById('email').value,
          captcha_challenge_id: challengeId,
          captcha_solution:     solution,
        }),
      });
    }
  };
</script>
Both modes work together. If the container is inside a form, hidden inputs are always injected and onSuccess is called. You can use whichever delivery path suits your stack — or both.
3

Verify on your server

When your backend receives captcha_challenge_id and captcha_solution, call POST /v1/captcha/verify before processing the request. Never call this endpoint from the browser.

func verifyCaptcha(r *http.Request) (bool, error) {
    challengeID := r.FormValue("captcha_challenge_id")
    solution    := r.FormValue("captcha_solution")
    if challengeID == "" || solution == "" {
        return false, nil
    }

    body, _ := json.Marshal(map[string]string{
        "client_id":    "YOUR_CLIENT_ID",
        "challenge_id": challengeID,
        "solution":     solution,
    })
    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
$challengeId = $_POST['captcha_challenge_id'] ?? '';
$solution    = $_POST['captcha_solution']     ?? '';
if (empty($challengeId) || empty($solution)) {
    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',
        'challenge_id' => $challengeId,
        'solution'     => $solution,
    ]),
]);
$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:
    challenge_id = form_data.get("captcha_challenge_id", "")
    solution     = form_data.get("captcha_solution", "")
    if not challenge_id or not solution:
        return False

    resp = requests.post(
        "https://basharyab.com/v1/captcha/verify",
        headers={"X-Client-Secret": "YOUR_CLIENT_SECRET"},
        json={
            "client_id":    "YOUR_CLIENT_ID",
            "challenge_id": challenge_id,
            "solution":     solution,
        },
        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",
    "challenge_id": "550e8400-e29b-41d4-a716-446655440000",
    "solution":     "abc123"
  }'

Response

// Success
{ "success": true, "valid": true }

// Challenge not found or expired (2-min TTL)
{ "error": "challenge not found" }        // 404

// Challenge already used — replay blocked
{ "error": "challenge already used" }     // 409

// Wrong credentials
{ "error": "invalid credentials" }        // 401
⚠️
Each challenge is consumed on first use. A second verify call with the same challenge_id returns 409 — 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/verify Backend only X-Client-Secret Confirm challenge_id + solution — returns { valid: true }

Credentials

CredentialHeader / FieldUsed onExpose in browser?
API Key X-API-Key /issue ✓ 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/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
410/verifyChallenge expired (2-min TTL)
429anyRate limit or monthly quota exceeded