Server Verification

How to securely verify NeoCaptcha tokens on your backend.

Updated 2/22/2026

Server Verification

Server verification is the core security boundary in NeoCaptcha integrations.

Rule: Never trust frontend success alone. Always verify the received token on your backend before allowing sensitive actions.


API reference

POST https://neocaptcha.com/api/v1/verify

Accepts application/json or application/x-www-form-urlencoded.

Request body

FieldTypeRequiredDescription
solutionstringYesThe verification token from the widget
secretstringYesYour site secret
siteKeystringYesYour site key
expectedOriginstringNoOrigin to validate against (e.g. https://app.example.com)

Response

FieldTypeDescription
successbooleantrue if the token is valid
verifiedAtstringISO timestamp of verification

End-to-end verification flow

  1. User completes widget challenge in browser.
  2. Widget emits a verificationToken.
  3. Frontend sends token to your backend route.
  4. Your backend calls POST /api/v1/verify.
  5. NeoCaptcha returns verification result with metadata.
  6. Your backend enforces policy and either accepts or rejects the request.

Minimal Node/TypeScript verification helper

export type VerifyCaptchaInput = {
  response: string;
  expectedOrigin?: string;
};
 
export async function verifyCaptcha(input: VerifyCaptchaInput) {
  const res = await fetch('https://neocaptcha.com/api/v1/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      solution: input.response,
      secret: process.env.NEOCAPTCHA_SITE_SECRET,
      siteKey: process.env.NEOCAPTCHA_SITE_KEY,
      expectedOrigin: input.expectedOrigin,
    }),
  });
 
  if (!res.ok) {
    throw new Error(`NeoCaptcha verify error: ${res.status}`);
  }
 
  return res.json();
}

Example route policy

app.post('/api/signup', async (req, res) => {
  const result = await verifyCaptcha({
    response: req.body.captchaToken,
    expectedOrigin: 'https://app.example.com',
  });
 
  if (!result.success) {
    return res.status(400).json({ error: 'captcha_invalid' });
  }
 
  // Continue with user creation
  return res.status(200).json({ ok: true });
});

Replay protection and token lifecycle

NeoCaptcha verification tokens are one-time credentials — each token can only be verified once.

Implications for integrators:

  • Do not cache tokens for reuse.
  • Do not allow "retry submit" with stale form payloads containing old token values.
  • If your action fails after captcha validation due to unrelated errors, ask the user to re-run the captcha before the next sensitive attempt.

Error handling guidance

Treat verification errors differently by category:

  • Invalid token: return user-facing "please retry captcha."
  • Expired token: prompt widget refresh and challenge re-run.
  • Origin mismatch: investigate integration or abuse attempts.
  • Verification API unavailable: fail closed for high-risk routes, fail open only for low-risk and with logging.

Language examples

Python (FastAPI)

import httpx
from fastapi import HTTPException
 
async def verify_captcha(token: str):
    async with httpx.AsyncClient(timeout=5.0) as client:
        response = await client.post(
            'https://neocaptcha.com/api/v1/verify',
            json={
                'solution': token,
                'secret': NEOCAPTCHA_SITE_SECRET,
                'siteKey': NEOCAPTCHA_SITE_KEY,
                'expectedOrigin': 'https://app.example.com',
            },
        )
 
    data = response.json()
    if not data.get('success'):
        raise HTTPException(status_code=400, detail='Captcha verification failed')
 
    return data

Go

payload := map[string]string{
  "solution":       token,
  "secret":         os.Getenv("NEOCAPTCHA_SITE_SECRET"),
  "siteKey":        os.Getenv("NEOCAPTCHA_SITE_KEY"),
  "expectedOrigin": "https://app.example.com",
}
 
b, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://neocaptcha.com/api/v1/verify", bytes.NewBuffer(b))
req.Header.Set("Content-Type", "application/json")

Security checklist

  • Site secret stored in backend secret manager, never exposed to the browser.
  • Every sensitive route verifies captcha before side effects.
  • expectedOrigin enforced.
  • Replay tokens rejected by design and in your flow logic.
  • Observability added for failure rates and abuse spikes.