Verify Endpoint
Server-side verification of CAPTCHA tokens.
Endpoint
POST https://gkcaptcha.gatekeeper.sa/api/v1/token/verifyRequest
cURL
curl -X POST https://gkcaptcha.gatekeeper.sa/api/v1/token/verify \
-H "Content-Type: application/json" \
-d '{
"siteKey": "pk_live_...",
"secret": "sk_live_...",
"token": "CAPTCHA_TOKEN_FROM_CLIENT"
}'Send the JSON body from your server only. Accept the visitor only when success is true and decision is not block.
Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Yes | Must be application/json |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| siteKey | string | Yes | Your site key (starts with pk_live_) |
| secret | string | Yes | Your secret key. Send it from your server only, never from the browser. |
| token | string | Yes | The token from the widget (the gk-captcha-token form field) |
| clientIP | string | No | The visitor's IP address, for token binding checks |
| userAgent | string | No | The visitor's User-Agent header, for token binding checks |
Response
Success Response
json
{
"success": true,
"score": 0.15,
"timestamp": 1705314600,
"decision": "allow"
}Failed Verification
json
{
"success": false,
"score": 0,
"timestamp": 0,
"error": "invalid or expired token",
"reasonCode": "token_expired"
}Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Whether the token is authentic and valid for this site |
| score | float | Risk score (0.0 = safe, 1.0 = bot). It is 0 when the response carries a decision. |
| timestamp | integer | When the token was issued (Unix seconds) |
| decision | string | The verdict band: allow, borderline or block. Reject the request when it is block, even though success is true. |
| error | string | Why the verification failed (only when success is false) |
| reasonCode | string | A machine-readable reason for the failure. See the reason code table. |
Error Codes
| Code | Description |
|---|---|
| invalid request body | The body is not valid JSON or misses siteKey, secret or token |
| invalid credentials | The site key or the secret key is wrong |
| site is disabled | The site is disabled in the dashboard |
| invalid or expired token | The token signature is invalid or the token expired |
| token not valid for this site | The token was issued for a different site key |
| token already used | The token was already verified once. Each token works once. |
| token binding validation failed | The clientIP or userAgent does not match the visitor who solved the CAPTCHA |
| verification service error | A temporary server error. Retry the request. |
Code Examples
Node.js / Express
server.js
// Never send your secret key to the browser.
const verifyCaptcha = async (token) => {
const response = await fetch('https://gkcaptcha.gatekeeper.sa/api/v1/token/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
siteKey: process.env.GKCAPTCHA_SITE_KEY,
secret: process.env.GKCAPTCHA_SECRET_KEY,
token,
}),
});
// { success, score, timestamp, decision?, error?, reasonCode? }
return response.json();
};
// Usage in Express route. The form arrives URL-encoded:
// app.use(express.urlencoded({ extended: false }))
app.post('/submit-form', async (req, res) => {
const token = req.body['gk-captcha-token'];
const result = await verifyCaptcha(token);
// success only means the token is authentic. A "block" decision must be rejected.
if (!result.success || result.decision === 'block') {
return res.status(400).json({ error: 'CAPTCHA verification failed' });
}
// Process the form...
res.json({ success: true });
});Go
main.go
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
type VerifyRequest struct {
SiteKey string `json:"siteKey"`
Secret string `json:"secret"`
Token string `json:"token"`
}
type VerifyResponse struct {
Success bool `json:"success"`
Score float64 `json:"score"`
Timestamp int64 `json:"timestamp"`
Decision string `json:"decision"`
Error string `json:"error"`
ReasonCode string `json:"reasonCode"`
}
// verifyCaptcha returns true only when the token is valid and not blocked.
func verifyCaptcha(token string) (bool, error) {
reqBody, _ := json.Marshal(VerifyRequest{
SiteKey: os.Getenv("GKCAPTCHA_SITE_KEY"),
Secret: os.Getenv("GKCAPTCHA_SECRET_KEY"),
Token: token,
})
resp, err := http.Post("https://gkcaptcha.gatekeeper.sa/api/v1/token/verify",
"application/json", bytes.NewBuffer(reqBody))
if err != nil {
return false, err
}
defer resp.Body.Close()
var result VerifyResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, err
}
// success only means the token is authentic. A "block" decision must be rejected.
if !result.Success || result.Decision == "block" {
return false, nil
}
return true, nil
}Python / Flask
app.py
import os
import requests
def verify_captcha(token: str) -> bool:
response = requests.post(
'https://gkcaptcha.gatekeeper.sa/api/v1/token/verify',
json={
'siteKey': os.environ['GKCAPTCHA_SITE_KEY'],
'secret': os.environ['GKCAPTCHA_SECRET_KEY'],
'token': token,
},
timeout=10,
)
result = response.json()
# success only means the token is authentic. A "block" decision must be rejected.
if not result.get('success') or result.get('decision') == 'block':
return False
return True
# Usage in Flask
@app.route('/submit', methods=['POST'])
def submit_form():
token = request.form.get('gk-captcha-token', '')
if not verify_captcha(token):
return jsonify({'error': 'Verification failed'}), 400
# Process form...
return jsonify({'success': True})Best Practices
- Always verify tokens server-side, never trust client-side results
- Handle errors gracefully - don't block users on API failures
- Set appropriate timeouts (recommended: 10 seconds)
- Log verification failures for security monitoring