@gkcaptcha/node
v1.0.0
Published
Node.js server SDK for gkCAPTCHA token verification
Readme
@gkcaptcha/node
Node.js server SDK for gkCAPTCHA token verification.
Requirements
- Node.js >= 18 (or Bun / Deno)
- Zero runtime dependencies (uses native
fetch)
Installation
npm install @gkcaptcha/nodeQuickstart
GatekeeperClient (recommended for apps with frequent verification)
import { GatekeeperClient } from '@gkcaptcha/node'
// Create once, reuse across requests
const gatekeeper = new GatekeeperClient({
siteKey: process.env.GKCAPTCHA_SITE_KEY!,
secretKey: process.env.GKCAPTCHA_SECRET_KEY!,
})
// In your route handler:
const result = await gatekeeper.verifyToken(captchaToken)
if (!result.success) {
return res.status(403).json({ error: 'CAPTCHA verification failed' })
}createGatekeeper (reads env vars automatically)
import { createGatekeeper } from '@gkcaptcha/node'
// Reads GKCAPTCHA_SITE_KEY and GKCAPTCHA_SECRET_KEY from process.env
const gatekeeper = createGatekeeper()
const result = await gatekeeper.verifyToken(captchaToken)verifyToken (standalone, one-shot)
import { verifyToken } from '@gkcaptcha/node'
const result = await verifyToken(captchaToken, {
siteKey: process.env.GKCAPTCHA_SITE_KEY!,
secretKey: process.env.GKCAPTCHA_SECRET_KEY!,
})Response Shape
interface VerifyTokenResponse {
success: boolean // DERIVED pass signal: rawWireSuccess && allowed
allowed: boolean // DERIVED: rawWireSuccess && decision !== 'block' — gate on THIS
decision?: 'allow' | 'borderline' | 'block' // raw verdict band (step-up UX)
failOpen?: boolean // true only on a transport-error fail-open synth
score: number // 0.0 (human) to 1.0 (bot)
timestamp: number // Unix timestamp of token creation
reasonCode?: string // Why verification failed (if success=false)
}Gate access on result.allowed. A blocked verdict (decision: 'block') is never
reported as allowed, even when the raw wire success was true (the bank-critical case).
Error Posture (Phase 257 — CHANGED in 1.0.0)
| Failure | Posture | Result |
|---------|---------|--------|
| Missing / empty token | fail-CLOSED | success:false, allowed:false |
| HTTP 4xx response | fail-CLOSED | success:false, allowed:false |
| HTTP 5xx response (after retries) | fail-CLOSED | success:false, allowed:false |
| Transport error (connect/timeout/DNS — no HTTP response) | fail-OPEN (default) | success:true, allowed:true, failOpen:true |
⚠️ Transport errors now FAIL OPEN (they were fail-closed before 1.0.0). This preserves
availability when the CAPTCHA service is unreachable. Banks / strict integrators who need
the old block-on-error behavior must set failClosed: true (or check result.failOpen
and override):
const gatekeeper = new GatekeeperClient({
siteKey: process.env.GKCAPTCHA_SITE_KEY!,
secretKey: process.env.GKCAPTCHA_SECRET_KEY!,
failClosed: true, // transport errors → success:false (old behavior)
})An HTTP-error response and an empty token stay fail-closed regardless of failClosed.
Upgrading (0.x → 1.0.0)
- Transport errors now fail OPEN — set
failClosed: trueor gate on!result.failOpento restore block-on-error. - Gate on
result.allowed(not the raw wiresuccess). Adecision:'block'verdict is never allowed.decisiondrives borderline step-up UX.
Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| GKCAPTCHA_SITE_KEY | Yes | Site key from gatekeeper.sa dashboard |
| GKCAPTCHA_SECRET_KEY | Yes | Secret key from gatekeeper.sa dashboard |
| GKCAPTCHA_API_URL | No | Override API URL (default: https://gkcaptcha.gatekeeper.sa) |
Options
new GatekeeperClient({
siteKey: '...',
secretKey: '...',
apiUrl: 'https://gkcaptcha.gatekeeper.sa', // default
timeout: 5000, // ms, default: 5000
retries: 1, // default: 1
debug: false, // logs requests (never logs secrets), default: false
failClosed: false, // true = transport errors fail CLOSED (old behavior), default: false
})