Skip to main content
This guide walks you through adding two Ribaunt CAPTCHA endpoints to an Express.js server: one that issues signed proof-of-work challenges to the client, and one that verifies the solved solutions before you trust the request. Both endpoints are stateless — Ribaunt encodes all challenge state inside a signed JWT, so no database is required.

Prerequisites

  • Node.js 18 or later
  • express and ribaunt installed in your project
  • A RIBAUNT_SECRET environment variable set to a strong random string

Best practices

  • Rate limit the challenge endpoint. Use express-rate-limit to cap how many challenges a single IP can request per minute. Without this, an attacker can farm tokens for offline brute-forcing.
  • Link verification to a session. After a successful verifySolution call, return a signed JWT or set an HTTP-only cookie. Require that token on your protected form submission endpoint so you know the CAPTCHA was solved by the same user making the downstream request.
  • Validate inputs before calling createChallenge. If difficulty, amount, or ttlSeconds come from config or request data, check that they are positive finite integers first — invalid values throw.
  • Use replayPrevention: 'remote' for multi-instance or serverless deployments. The default 'local' store is per-process, so it cannot detect replays across separate Node.js processes. Pass a distributed store adapter (e.g. backed by Redis) and set replayPrevention: 'remote' in your verifySolution options.
  • Use onWarning for telemetry. The onWarning callback receives a structured VerifyWarning object with a typed reason ('invalid-token', 'expired-token', 'replay-detected', etc.) and a human-readable message. Wire it to your observability pipeline without enabling verbose debug logs in production.
Validate any user- or config-controlled difficulty, amount, and ttlSeconds values before passing them to createChallenge(). Invalid values throw.