Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions api/create-challenge.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "createChallenge"
sidebarTitle: "createChallenge"
description: "createChallenge() issues signed JWT challenge tokens for Ribaunt proof-of-work CAPTCHA verification on the server."

Check warning on line 4 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L4

Did you really mean 'createChallenge'?

Check warning on line 4 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L4

Did you really mean 'Ribaunt'?
---

`createChallenge()` is imported from `ribaunt` and called server-side to generate one or more proof-of-work challenge tokens. Each token is a signed JWT that the browser solver decodes and works against.
Expand Down Expand Up @@ -29,7 +29,7 @@
</Note>

<Note>
`difficulty` accepts either a positive integer or the string `"auto"`. In `"auto"` mode, Ribaunt picks a `difficulty` and `amount` at runtime using `selectWorkload()` based on an optional client `calibration`, a server-side `riskScore`, `targetDurationMs`, and the `min`/`max` bounds you configure. Calibration is treated as untrusted — a fast benchmark can only _raise_ work up to your maximums, never lower the server-owned baseline.

Check warning on line 32 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L32

Did you really mean 'Ribaunt'?

Check warning on line 32 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L32

Did you really mean 'untrusted'?

Check warning on line 32 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L32

Did you really mean '_raise_'?
</Note>

## Parameters
Expand All @@ -40,7 +40,7 @@
- an options object: `createChallenge({ difficulty, amount, ttlSeconds, context, workload })`

<ParamField path="difficulty" default="5" type='number | "auto"'>
Number of leading zero hex digits required in the hash. Each increment roughly doubles solve time. SHA-256 accepts `1`–`64` (values above 6 may cause browsers to hang); Argon2id accepts `1`–`8`. Pass `"auto"` to have Ribaunt select difficulty and amount adaptively — see [Adaptive workload](#adaptive-workload) below.

Check warning on line 43 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L43

Did you really mean 'Ribaunt'?
</ParamField>

<ParamField path="algorithm" default="sha256" type="'sha256' | 'argon2id'">
Expand All @@ -48,7 +48,7 @@
</ParamField>

<ParamField path="argonProfile" default="mobile" type="'mobile' | 'standard'">
Memory-hardness profile, only valid with `algorithm: 'argon2id'`. The profile abstracts the raw Argon2id parameters (`m`, `t`, `p`) so you never pass memory sizes yourself. Passing `argonProfile` with `algorithm: 'sha256'` throws.
Memory-hardness profile, only valid with `algorithm: 'argon2id'`. The profile abstracts the raw Argon2id parameters (`m`, `t`, `p`) so you never pass memory sizes yourself. Both profiles currently resolve to the same conservative tuning; pick the tier that matches your audience now, and future retuning will not break in-flight tokens because each token embeds its own parameters. Passing `argonProfile` with `algorithm: 'sha256'` throws.
</ParamField>

<ParamField path="amount" default="4" type="number">
Expand Down Expand Up @@ -80,7 +80,7 @@
</ParamField>

<ParamField path="calibration" type="ClientCalibration">
Untrusted client benchmark, typically forwarded from the widget when `challenge-method="POST"` and `calibrate="true"` are set. Used as a raise-only signal: fast calibration can increase work up to your maximum bounds, a slow or fake one cannot reduce it below the server baseline.

Check warning on line 83 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L83

Did you really mean 'Untrusted'?
</ParamField>

<ParamField path="minDifficulty" default="3" type="number">
Expand All @@ -102,7 +102,7 @@
### Hooks

<ParamField path="rateLimiter" type="RateLimiter">
Optional bring-your-own rate limiter. `createChallenge()` calls `rateLimiter.check(context)` before issuing tokens. If the limiter resolves `false`, Ribaunt throws a `RateLimitedError` (`code: 'rate-limited'`) and does not issue any tokens. Use this to reject abusive callers by IP, session, or user before spending JWT signing work.

Check warning on line 105 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L105

Did you really mean 'Ribaunt'?
</ParamField>

<ParamField path="onEvent" type="(event: RibauntEvent) => void">
Expand Down Expand Up @@ -167,7 +167,7 @@
```

<Warning>
Key the limiter on the client IP by closing over the request, as shown above. Do not pass `context: req.ip` for rate limiting. `context` cryptographically binds every token to that value, so `verifySolution()` must receive the exact same `context` or verification fails with `context-mismatch`. Reserve `context` for binding a challenge to a specific action, such as one signup attempt.

Check warning on line 170 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L170

Did you really mean 'signup'?
</Warning>

### Telemetry with `onEvent`
Expand Down Expand Up @@ -226,12 +226,12 @@
`selectWorkload()` respects the configured bounds and returns a `Workload` object with `difficulty`, `amount`, `estimatedAttempts`, and `algorithm`. It accepts the same `algorithm` and `argonProfile` options as `createChallenge()`; for `argon2id` the result also includes the resolved `argon` parameters.

<Note>
To derive `riskScore` from application signals such as account age or request velocity instead of hardcoding it, use [`assess()`](/api/assess). When it recommends a challenge, it returns a ready-made `Workload` you can pass here.

Check warning on line 229 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L229

Did you really mean 'hardcoding'?
</Note>

### Calibration helpers

Ribaunt exposes calibration helpers for both environments so you can benchmark the runtime that will actually solve the challenge:

Check warning on line 234 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L234

Did you really mean 'Ribaunt'?

```ts
import { calibrateNode, calibrateClient } from 'ribaunt'; // Node.js, SHA-256
Expand All @@ -241,19 +241,19 @@
import { calibrateArgonBrowser, calibrateArgonClient } from 'ribaunt/widget'; // browser, Argon2id
```

`calibrateClient` and `calibrateArgonClient` are cross-environment aliases — bundlers resolve the correct implementation via the package export map.

Check warning on line 244 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L244

Did you really mean 'bundlers'?

<Warning>
Use the calibrator that matches your `algorithm`. A SHA-256 calibration passed to an `argon2id` workload (or vice versa) will over- or under-estimate the device and skew the selected difficulty.
</Warning>

<Warning>
Any `calibration` value coming from a browser request is untrusted. Ribaunt only uses it to raise work above the server-owned baseline, but you should still validate its shape before passing it through.

Check warning on line 251 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L251

Did you really mean 'untrusted'?

Check warning on line 251 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L251

Did you really mean 'Ribaunt'?
</Warning>

## Argon2id opt-in

By default, Ribaunt uses SHA-256, which hashes in microseconds and keeps server-side verification cheap. Opt in to `argon2id` when you want a memory-hard proof of work: each hash allocates a fixed amount of memory, which makes large-scale solving on GPUs and ASICs far more expensive relative to a real user's browser.

Check warning on line 256 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L256

Did you really mean 'Ribaunt'?

Check warning on line 256 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L256

Did you really mean 'GPUs'?

Check warning on line 256 in api/create-challenge.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/create-challenge.mdx#L256

Did you really mean 'ASICs'?

```ts
import { createChallenge, calibrateArgonNode } from 'ribaunt';
Expand All @@ -271,12 +271,16 @@

- **Difficulty scale.** Each Argon2id hash takes milliseconds instead of microseconds, so difficulty caps at `8` and `"auto"` bounds default to `1`–`2` (versus `64` and `3`–`6` for SHA-256).
- **Profiles instead of raw parameters.** `argonProfile` resolves the Argon2id memory, iteration, and parallelism parameters for you. The library enforces a hard upper bound (`HARD_MAX`, exported from `ribaunt`) on those parameters, and tokens carrying values above it are rejected as `invalid-token`.
- **Tokens are self-describing.** Each challenge token carries its algorithm and Argon2id parameters, signed into the JWT. `verifySolution()` detects the algorithm per token, so your verify endpoint needs no changes.
- **Tokens are self-describing.** Each challenge token carries its algorithm, its Argon2id parameters, and a construction version (`v: 1`), signed into the JWT. `verifySolution()` detects the algorithm per token, so your verify endpoint needs no changes, and future profile retuning cannot break tokens that are already in flight.
- **Browser support is automatic.** The widget and its solver worker detect the algorithm per token and load the Argon2id solver on demand. The [`solver-backend` event](/widget/events#solver-backend) reports `argon2id` so you can confirm it in telemetry.
- **Testing.** The synchronous [`solveChallenge()`](/api/solve-challenge) helper supports SHA-256 only. Use `solveChallengeAsync()` in tests that solve `argon2id` tokens.

The adaptive engine works the same for both algorithms: `riskScore` and `calibration` remain raise-only signals within your configured bounds. [`assess()`](/api/assess) accepts `algorithm` and `argonProfile` in its `workload` options when you want the risk engine to produce Argon2id workloads.

<Warning>
Argon2id narrows the attacker's GPU and ASIC advantage but does not eliminate it. A well-funded adversary can still solve any proof-of-work challenge, just at a higher memory cost. Treat it as one abuse-cost layer alongside rate limiting and risk signals, not as human verification or the sole gate for sensitive actions.
</Warning>

## Validation

`createChallenge()` validates its inputs at runtime and throws if anything is invalid:
Expand Down
2 changes: 1 addition & 1 deletion api/types.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: "Types"
sidebarTitle: "Types"
description: "Complete TypeScript type reference for Ribaunt: ChallengeToken, ChallengeSolution, RiskSignals, ReplayStore, VerifySolutionOptions, and more."

Check warning on line 4 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L4

Did you really mean 'Ribaunt'?
---

Ribaunt ships TypeScript types for all its public APIs. Import types from `ribaunt` for use in your server-side code.

Check warning on line 7 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L7

Did you really mean 'Ribaunt'?

## `ChallengeToken`

Expand All @@ -29,7 +29,7 @@

`PowAlgorithm` selects the proof-of-work algorithm for `createChallenge()` and `selectWorkload()`. The default is `'sha256'`; `'argon2id'` is a memory-hard opt-in — see [Argon2id opt-in](/api/create-challenge#argon2id-opt-in).

`ArgonProfile` abstracts the raw Argon2id parameters so you never pass memory sizes yourself. It is only valid when the algorithm is `'argon2id'`.
`ArgonProfile` abstracts the raw Argon2id parameters so you never pass memory sizes yourself. It is only valid when the algorithm is `'argon2id'`. Both profiles currently resolve to the same conservative parameters (`m: 8192`, `t: 1`, `p: 1`, `hashLen: 32`). Because each token embeds its own parameters and a construction version, choosing a tier now is safe: retuning in a future release cannot break in-flight tokens.

```ts
type PowAlgorithm = 'sha256' | 'argon2id';
Expand Down Expand Up @@ -111,7 +111,7 @@

## `RiskSignals`

Caller-supplied signals passed to [`assess()`](/api/assess). All fields are optional and treated as untrusted. The index signature lets custom scorers consume application-specific keys; the default scorer ignores unknown keys.

Check warning on line 114 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L114

Did you really mean 'untrusted'?

```ts
interface RiskSignals {
Expand Down Expand Up @@ -211,7 +211,7 @@
});
```

`LocalReplayStore` is not suitable for multi-process or serverless deployments. For those environments, implement your own `ReplayStore` backed by a distributed atomic store such as Redis `SET NX EXAT`.

Check warning on line 214 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L214

Did you really mean 'serverless'?

## `ReplayPreventionMode`

Expand All @@ -222,7 +222,7 @@
```

- **`'local'`** (default) — replay checks are process-local. Suitable for single-process deployments.
- **`'remote'`** — replay checks use your `ReplayStore`. Use this for serverless or horizontally scaled deployments.

Check warning on line 225 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L225

Did you really mean 'serverless'?
- **`'disabled'`** — no replay checks. Tokens can be reused until they expire. Only use this if another layer handles replay prevention.

## `VerifySolutionOptions`
Expand All @@ -243,7 +243,7 @@

## `RateLimiter`

The interface you implement to plug your own rate limiter into `createChallenge()` and `verifySolution()`. Ribaunt calls `check()` with the optional `context` value from the same call. Resolve `true` to allow, `false` to reject. Rejection surfaces as a thrown [`RateLimitedError`](#ratelimitederror).

Check warning on line 246 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L246

Did you really mean 'Ribaunt'?

```ts
interface RateLimiter {
Expand Down Expand Up @@ -280,7 +280,7 @@

## `RibauntEvent`

Discriminated union passed to the optional `onEvent` telemetry hook on both `createChallenge()` and `verifySolution()`. Use it to feed metrics or logs without wrapping Ribaunt yourself.

Check warning on line 283 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L283

Did you really mean 'Ribaunt'?

```ts
type RibauntEvent =
Expand Down Expand Up @@ -334,7 +334,7 @@
type VerifyWarningReason = VerifyFailureReason;
```

`replay-store-unavailable` is reported when your `ReplayStore` throws (for example, Redis is unreachable). Verification fails closed with this reason instead of miscategorizing the outage as `invalid-token`, so you can alert on store health separately from bad submissions.

Check warning on line 337 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L337

Did you really mean 'miscategorizing'?

## `SolveChallengeOptions`

Expand All @@ -357,7 +357,7 @@

## `WidgetErrorCode`

Machine-readable error classification emitted on the widget's `error` event `detail.code`.

Check warning on line 360 in api/types.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/types.mdx#L360

Did you really mean 'widget's'?

```ts
type WidgetErrorCode =
Expand Down
2 changes: 1 addition & 1 deletion api/verify-solution.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "verifySolution"
sidebarTitle: "verifySolution"
description: "verifySolution() validates JWT signatures, hash proofs, expiry, replay protection, and optional context binding. Returns a structured result object."

Check warning on line 4 in api/verify-solution.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

api/verify-solution.mdx#L4

Did you really mean 'verifySolution'?
---

`verifySolution()` is called server-side in your verify endpoint to check that the browser correctly solved all challenge tokens. It validates the JWT signature, token expiry, the hash proof, replay state, and optional context binding. Each token carries its own algorithm, so `verifySolution()` verifies SHA-256 and [Argon2id](/api/create-challenge#argon2id-opt-in) proofs automatically with no extra options.
Expand Down Expand Up @@ -148,7 +148,7 @@

| Reason | Description |
| --- | --- |
| `invalid-token` | JWT signature is invalid or malformed |
| `invalid-token` | JWT signature is invalid, the payload is malformed, or the token carries an unknown construction version (`v`) |
| `expired-token` | Challenge TTL has passed |
| `invalid-solution` | The nonce does not produce a valid hash |
| `context-mismatch` | The supplied context does not match the token's bound context |
Expand Down
16 changes: 16 additions & 0 deletions changelog/overview.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
---
title: "Changelog"
description: "New features, updates, and fixes to Ribaunt."

Check warning on line 3 in changelog/overview.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

changelog/overview.mdx#L3

Did you really mean 'Ribaunt'?
mode: "center"
---

<Update label="v0.2.6">
## Improvements

**Challenge token construction version**

Every challenge token now embeds a construction version (`v: 1`) that pins how the solution is computed. [`verifySolution`](/api/verify-solution) rejects tokens with an unknown future version as `invalid-token`, and the solvers return `undefined` instead of solving under wrong assumptions. Tokens issued before this release (no `v` field) are treated as version 1 and still verify, so no action is needed.

**Argon2id profile tiers clarified**

The `'mobile'` and `'standard'` values of [`argonProfile`](/api/create-challenge#argon2id-opt-in) currently share the same conservative tuning. Pick the tier that matches your audience now; because each token embeds its own parameters and version, future retuning cannot break in-flight tokens.

## Fixes

- **Argon2id salt entropy.** Challenge strings now carry 16 random bytes (128 bits) of entropy, and the Argon2id salt is derived from the full-entropy challenge instead of a shorter zero-padded value. Tokens issued before this fix still verify during their TTL window.
</Update>

<Update label="v0.2.5">
## New

**Argon2id memory-hard proof-of-work (opt-in)**

[`createChallenge`](/api/create-challenge) accepts a new `algorithm: 'argon2id'` option alongside the default `sha256`. Argon2id is memory-hard, which raises the cost of solving challenges on GPUs and custom hardware. Pick a memory preset with `argonProfile` (`'mobile'` or `'standard'`) instead of raw memory parameters, and Ribaunt enforces hard upper bounds on them. SHA-256 stays the default, so existing setups are unaffected.

Check warning on line 28 in changelog/overview.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

changelog/overview.mdx#L28

Did you really mean 'GPUs'?

Check warning on line 28 in changelog/overview.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

changelog/overview.mdx#L28

Did you really mean 'Ribaunt'?

**`solveChallengeAsync`**

Expand All @@ -27,7 +43,7 @@

## Fixes

- The widget's WebAssembly solver now validates the fetched binary before using it. Dev servers with SPA fallbacks (such as Vite) that answer the WASM request with an HTML page no longer break solving; the widget falls back to the JavaScript solver instead.

Check warning on line 46 in changelog/overview.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

changelog/overview.mdx#L46

Did you really mean 'widget's'?
</Update>

<Update label="v0.2.4">
Expand All @@ -35,7 +51,7 @@

**WebAssembly solver with automatic fallback**

The widget's solver worker now includes a WebAssembly-backed SHA-256 batch solver for faster proof-of-work solving. It is enabled by default and falls back to the JavaScript solver automatically when WebAssembly is unavailable, so no changes are required. Control it with the new [`wasm-mode`](/widget/configuration#wasm-solver) attribute (`wasmMode` prop in React), which is independent from `worker-mode`.

Check warning on line 54 in changelog/overview.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

changelog/overview.mdx#L54

Did you really mean 'widget's'?

**`solver-backend` event**

Expand All @@ -51,7 +67,7 @@

**Machine-readable widget error codes**

The widget's `error` event now includes a [`WidgetErrorCode`](/api/types#widgeterrorcode), so you can branch on the failure cause programmatically instead of parsing error messages. See [Widget events](/widget/events#error).

Check warning on line 70 in changelog/overview.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

changelog/overview.mdx#L70

Did you really mean 'widget's'?

**Widget types exported**

Expand Down
5 changes: 4 additions & 1 deletion concepts/how-it-works.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: "How Ribaunt Works"

Check warning on line 2 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L2

Did you really mean 'Ribaunt'?
sidebarTitle: "How It Works"
description: "Understand the proof-of-work challenge-response flow behind Ribaunt: JWT challenge issuance, browser solving with Web Crypto, and server-side verification."

Check warning on line 4 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L4

Did you really mean 'Ribaunt'?

Check warning on line 4 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L4

Did you really mean 'Crypto'?
---

Ribaunt protects your forms and sensitive actions by making the browser perform a small but measurable amount of computational work before your server accepts a submission. Unlike traditional CAPTCHAs, this happens silently in the background: your server issues a cryptographic puzzle, the browser grinds through SHA-256 hashes until it finds a valid answer, and your server checks the proof before proceeding. No image grids, no checkbox rituals — just math.

Check warning on line 7 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L7

Did you really mean 'Ribaunt'?

Check warning on line 7 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L7

Did you really mean 'CAPTCHAs'?

## The challenge-response flow

Every Ribaunt verification follows a three-step cycle.

Check warning on line 11 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L11

Did you really mean 'Ribaunt'?

<Steps>
<Step title="Server issues challenge tokens">
Expand All @@ -25,7 +25,7 @@
```
</Step>
<Step title="Browser solves the challenges">
The browser receives the tokens and runs the solver. For each token it decodes the challenge string and difficulty, then iterates through candidate nonces — `0`, `1`, `2`, … — computing `SHA-256(challenge + nonce)` for each one using the Web Crypto API. The moment it finds a nonce whose hash starts with the required number of leading zeros, it records that nonce and hash as the solution and moves on to the next token.

Check warning on line 28 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L28

Did you really mean 'nonces'?

Check warning on line 28 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L28

Did you really mean 'Crypto'?
</Step>
<Step title="Server verifies the solutions">
The browser submits the original tokens alongside the solutions it found. Your server calls `verifySolution()`, which:
Expand Down Expand Up @@ -55,12 +55,15 @@

| Field | Description |
| --- | --- |
| `challenge` | A random base64 string generated fresh for each token |
| `challenge` | A random 128-bit base64url string (16 random bytes) generated fresh for each token |
| `difficulty` | The number of leading zeros the SHA-256 hash must start with |
| `expires` | A Unix timestamp (seconds) after which the token is rejected |
| `jti` | A UUID that uniquely identifies this token, used for replay prevention |
| `v` | The construction version, currently `1`. It pins how the solution is computed, so future changes cannot break in-flight tokens. |

Tokens with an unknown future `v` value fail closed: `verifySolution()` rejects them as `invalid-token`, and the solvers return `undefined` instead of solving under wrong assumptions. Tokens issued before versioning (no `v` field) are treated as version 1 and still verify.

Ribaunt hashes with SHA-256 by default. If you opt in to the memory-hard [Argon2id algorithm](/api/create-challenge#argon2id-opt-in), each token additionally carries the algorithm identifier and its Argon2id parameters, and the solver and `verifySolution()` detect the algorithm per token automatically.

Check warning on line 66 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L66

Did you really mean 'Ribaunt'?

The tokens are signed with `RIBAUNT_SECRET`, an environment variable you set on your server. Your server is the only party that can produce or validate tokens — the browser only ever sees the signed, opaque JWT.

Expand All @@ -84,12 +87,12 @@

## Stateless design

Ribaunt does not require a database to issue or verify challenges. All the information the server needs to verify a solution — the challenge string, the required difficulty, and the expiry — is encoded directly in the signed JWT token. Verification reduces to checking the JWT signature and recomputing the hash; no round-trips to a data store are necessary for the core proof check.

Check warning on line 90 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L90

Did you really mean 'Ribaunt'?

Replay protection is the one area that requires state. Without it, a single valid solution could be resubmitted repeatedly within the token's TTL window. Ribaunt handles this with a lightweight replay store:

Check warning on line 92 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L92

Did you really mean 'Ribaunt'?

- In the default `local` mode, an in-process `Map` tracks used token IDs within the current Node.js process.
- In `remote` mode, you supply a distributed store (such as Redis or Valkey) so that multiple processes or serverless function instances share a consistent view of which tokens have already been consumed.

Check warning on line 95 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L95

Did you really mean 'Valkey'?

Check warning on line 95 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L95

Did you really mean 'serverless'?

This keeps your server infrastructure simple — no session tables, no challenge databases — while still providing the replay protection that makes the scheme secure.

Expand All @@ -99,7 +102,7 @@

## Secure context requirement

The browser solver uses the Web Crypto API (`crypto.subtle`), which browsers only expose in [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). This means client-side solving works on:

Check warning on line 105 in concepts/how-it-works.mdx

View check run for this annotation

Mintlify / Mintlify Validation (ribaunt-e66481b6) - vale-spellcheck

concepts/how-it-works.mdx#L105

Did you really mean 'Crypto'?

- `https://` origins (production)
- `http://localhost` (local development)
Expand Down