Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
934c879
feat: add initial server package
LuggaPugga Mar 1, 2026
7adca12
refactor(tests): enhance provider tests with mock fetch and additiona…
LuggaPugga Mar 1, 2026
dd304cd
feat: add new utility functions and enhance provider verification logic
LuggaPugga Mar 1, 2026
4e84bf3
refactor: replace verification functions with a new utility for reCAP…
LuggaPugga Mar 1, 2026
8a506cf
refactor: simplify postJson function and remove jsonBody option
LuggaPugga Mar 1, 2026
88f4ac8
feat: add server verification documentation for @better-captcha/server
LuggaPugga Mar 1, 2026
18e3aed
refactor: introduce JsonObject type and utility functions for improve…
LuggaPugga Mar 1, 2026
6532073
refactor: streamline mergeAbortSignal function and remove unused JSON…
LuggaPugga Mar 1, 2026
cba1629
refactor: update package.json for ESM compatibility, improve error ha…
LuggaPugga Mar 1, 2026
4a44437
refactor: enhance error handling in verification callbacks and update…
LuggaPugga Mar 3, 2026
1355a70
feat: implement verifyCaptcha function and add corresponding tests fo…
LuggaPugga Mar 3, 2026
5d3571f
docs: add generic verification example for verifyCaptcha function in …
LuggaPugga Mar 3, 2026
d745227
refactor: improve abort signal handling in mergeAbortSignal function …
LuggaPugga Mar 3, 2026
44bedb9
feat: add typecheck script to package.json for TypeScript type checking
LuggaPugga Mar 12, 2026
4fe1f6f
refactor: simplify CaptchaServerError implementation by removing symb…
LuggaPugga Mar 31, 2026
3928122
refactor: enhance string validation and simplify endpoint checks in v…
LuggaPugga Mar 31, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ jobs:
- name: Install dependencies
run: bun install --frozen-lockfile

- name: Run unit tests
run: bun test packages/server/tests

- name: Build package
run: bun run build
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Install the package for your framework:
| Vue | `bun install @better-captcha/vue` | `npm install @better-captcha/vue` |
| Svelte | `bun install @better-captcha/svelte` | `npm install @better-captcha/svelte` |
| Lit | `bun install @better-captcha/lit` | `npm install @better-captcha/lit` |
| Server | `bun install @better-captcha/server` | `npm install @better-captcha/server` |


## Basic usage
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/content/docs/en/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This library is in early development and is not production ready yet. Expect bre
| **Vue** | `@better-captcha/vue` | `npm i @better-captcha/vue` |
| **Svelte** | `@better-captcha/svelte` | `npm i @better-captcha/svelte` |
| **Lit** | `@better-captcha/lit` | `npm i @better-captcha/lit` |
| **Server** | `@better-captcha/server` | `npm i @better-captcha/server` |

### 2. Import and use a provider

Expand Down Expand Up @@ -112,5 +113,6 @@ Each framework has its own idiomatic way of accessing the handle:

- Choose your [framework](/docs/frameworks) and learn the framework-specific patterns
- Explore [provider options](/docs/provider) for your chosen CAPTCHA service
- Add [server verification](/docs/server) before trusting tokens
- Configure [auto theme](/docs/auto-theme) for automatic dark/light mode support
- Customize [script loading](/docs/script-options) behavior
6 changes: 6 additions & 0 deletions apps/docs/content/docs/en/provider/altcha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,9 @@ Altcha uses an `endpoint` prop instead of `sitekey` to specify the challenge URL
- **Open Source**: Benefit from community contributions and transparency
- **Secure Context**: Requires HTTPS connection due to cryptographic API usage
- **Internationalization**: Supports 50+ languages with automatic language detection

## Server Verification

`@better-captcha/server` does not currently include a built-in Altcha verifier.

Verify the Altcha payload with your own endpoint logic (for example, your challenge verification endpoint), and reject the request when verification fails.
6 changes: 6 additions & 0 deletions apps/docs/content/docs/en/provider/cap-widget.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,9 @@ CapWidget uses an `endpoint` prop instead of `sitekey` to specify the API endpoi
- **Lightweight**: Minimal impact on page load times
- **Open Source**: Benefit from community contributions and transparency
- **Customization**: Customize the widget appearance to match your application's design

## Server Verification

`@better-captcha/server` does not currently include a built-in CapWidget verifier.

Verify tokens against your Cap.JS backend endpoint and treat the request as untrusted until your server confirms the token.
18 changes: 18 additions & 0 deletions apps/docs/content/docs/en/provider/captcha-fox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,21 @@ For more information, visit the [official Captcha Fox documentation](https://doc
- **Privacy**: Captcha Fox is designed with privacy in mind - no unnecessary tracking
- **Analytics**: Use the built-in analytics to understand bot traffic patterns
- **Auto Theme**: Use `theme: "auto"` to automatically match your site's color scheme

## Server Verification

Use `@better-captcha/server` to verify CaptchaFox tokens on your backend:

```ts
import { verifyCaptchaFox } from "@better-captcha/server";

const result = await verifyCaptchaFox({
endpoint: "https://api.captchafox.com/siteverify",
secret: process.env.CAPTCHAFOX_SECRET!,
response: tokenFromClient,
});

if (!result.success) {
console.error(result.errorCodes);
}
```
18 changes: 18 additions & 0 deletions apps/docs/content/docs/en/provider/friendly-captcha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,21 @@ For more information, visit the [official Friendly Captcha documentation](https:
- **Accessibility**: Fully accessible - no visual challenges that could exclude users
- **Performance**: Minimal impact on page performance, works in background
- **EU Hosting**: Data can be hosted in the EU for additional privacy compliance

## Server Verification

Use `@better-captcha/server` to verify Friendly Captcha tokens on your backend:

```ts
import { verifyFriendlyCaptcha } from "@better-captcha/server";

const result = await verifyFriendlyCaptcha({
apiKey: process.env.FRIENDLY_CAPTCHA_API_KEY!,
response: tokenFromClient,
sitekey: process.env.FRIENDLY_CAPTCHA_SITE_KEY,
});

if (!result.success) {
console.error(result.errorCodes);
}
```
19 changes: 19 additions & 0 deletions apps/docs/content/docs/en/provider/hcaptcha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,22 @@ Test keys are designed for development and automated testing. Replace with real
- **Difficulty Levels**: Adjust difficulty based on your security needs vs. user experience
- **Passive Mode**: Use passive mode for lower-risk scenarios to improve UX
- **Auto Theme**: Use `theme: "auto"` to match your site's color scheme automatically

## Server Verification

Use `@better-captcha/server` to verify hCaptcha tokens on your backend:

```ts
import { verifyHCaptcha } from "@better-captcha/server";

const result = await verifyHCaptcha({
secret: process.env.HCAPTCHA_SECRET_KEY!,
response: tokenFromClient,
sitekey: process.env.HCAPTCHA_SITE_KEY,
expectedHostname: "example.com",
});

if (!result.success) {
console.error(result.errorCodes);
}
```
18 changes: 18 additions & 0 deletions apps/docs/content/docs/en/provider/private-captcha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,21 @@ Private Captcha only works inside a `<form>` element. Make sure to wrap the comp
- **Self-Hosting**: Consider self-hosting for maximum control and data sovereignty
- **Auto Theme**: Use `theme: "auto"` to match your site's color scheme
- **Accessibility**: Built with accessibility in mind for inclusive user experiences

## Server Verification

Use `@better-captcha/server` to verify Private Captcha tokens on your backend:

```ts
import { verifyPrivateCaptcha } from "@better-captcha/server";

const result = await verifyPrivateCaptcha({
endpoint: "https://verify.privatecaptcha.com/siteverify",
secret: process.env.PRIVATE_CAPTCHA_SECRET!,
response: tokenFromClient,
});

if (!result.success) {
console.error(result.errorCodes);
}
```
18 changes: 18 additions & 0 deletions apps/docs/content/docs/en/provider/prosopo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,21 @@ For more information, visit the [official Prosopo documentation](https://docs.pr
- **Decentralization**: No central authority controlling the CAPTCHA service
- **Challenge Types**: Choose between frictionless and proof-of-work based on your needs
- **Auto Theme**: Use `theme: "auto"` to match your site's color scheme

## Server Verification

Use `@better-captcha/server` to verify Prosopo tokens on your backend:

```ts
import { verifyProsopo } from "@better-captcha/server";

const result = await verifyProsopo({
endpoint: "https://verify.prosopo.io/siteverify",
secret: process.env.PROSOPO_SECRET!,
response: tokenFromClient,
});

if (!result.success) {
console.error(result.errorCodes);
}
```
19 changes: 19 additions & 0 deletions apps/docs/content/docs/en/provider/recaptcha-v3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,22 @@ For more information, visit the [official Google reCAPTCHA v3 documentation](htt
- **Token Caching**: The provider automatically caches tokens for 2 minutes to reduce API calls
- **Multiple Actions**: Use different actions for different pages/interactions to get better analytics


## Server Verification

Use `@better-captcha/server` to verify reCAPTCHA v3 tokens on your backend:

```ts
import { verifyReCaptcha } from "@better-captcha/server";

const result = await verifyReCaptcha({
secret: process.env.RECAPTCHA_SECRET_KEY!,
response: tokenFromClient,
expectedAction: "submit",
minScore: 0.5,
});

if (!result.success) {
console.error(result.errorCodes);
}
```
18 changes: 18 additions & 0 deletions apps/docs/content/docs/en/provider/recaptcha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,21 @@ For more information, visit the [official Google reCAPTCHA documentation](https:
- **Verify Server-Side**: Always verify the response token on your backend
- **Consider UX**: For v2 invisible, ensure users understand why they're seeing a challenge
- **Auto Theme**: Use `theme: "auto"` to match your site's color scheme automatically

## Server Verification

Use `@better-captcha/server` to verify reCAPTCHA tokens on your backend:

```ts
import { verifyReCaptcha } from "@better-captcha/server";

const result = await verifyReCaptcha({
secret: process.env.RECAPTCHA_SECRET_KEY!,
response: tokenFromClient,
expectedHostname: "example.com",
});

if (!result.success) {
console.error(result.errorCodes);
}
```
19 changes: 19 additions & 0 deletions apps/docs/content/docs/en/provider/turnstile.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,22 @@ Test keys are designed for development and automated testing. They will always b
- **Lightweight**: Turnstile has minimal impact on page load times
- **Privacy-First**: No tracking or cookies, making it ideal for privacy-conscious applications
- **Cloudflare Integration**: Works seamlessly with other Cloudflare services

## Server Verification

Use `@better-captcha/server` to verify Turnstile tokens on your backend:

```ts
import { verifyTurnstile } from "@better-captcha/server";

const result = await verifyTurnstile({
secret: process.env.TURNSTILE_SECRET_KEY!,
response: tokenFromClient,
expectedHostname: "example.com",
expectedAction: "signup",
});

if (!result.success) {
console.error(result.errorCodes);
}
```
118 changes: 118 additions & 0 deletions apps/docs/content/docs/en/server.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
title: Server Verification
description: Verify CAPTCHA tokens on your backend with @better-captcha/server.
---

## Installation

```sh
npm i @better-captcha/server
```

## Why

Client-side CAPTCHA widgets only issue tokens. You must verify those tokens on your backend before trusting the request.

## Quick Start

```ts
import { verifyToken } from "@better-captcha/server";

export async function POST(request: Request) {
const body = await request.json();
const token = body.captchaToken;

const result = await verifyToken("turnstile", {
secret: process.env.TURNSTILE_SECRET_KEY!,
response: token,
expectedHostname: "example.com",
});

if (!result.success) {
return Response.json({ ok: false, errors: result.errorCodes }, { status: 400 });
}

return Response.json({ ok: true });
}
```

## Generic Verification

Use `verifyCaptcha` for a single-object API where the provider is part of the options:

```ts
import { verifyCaptcha } from "@better-captcha/server";

const result = await verifyCaptcha({
provider: "turnstile",
secret: process.env.TURNSTILE_SECRET_KEY!,
response: token,
expectedHostname: "example.com",
});
```

The options and return type are inferred from the `provider` field — each provider surfaces its own typed options and result.

## Verification Callbacks

All server verifiers support optional `onSuccess` and `onError` callbacks:

```ts
import { verifyTurnstile } from "@better-captcha/server";

await verifyTurnstile({
secret: process.env.TURNSTILE_SECRET_KEY!,
response: token,
onSuccess: (result) => {
console.log("ok", result.data.hostname);
},
onError: (result) => {
console.log("failed", result.errorCodes);
},
});
```

## Built-in Providers

| Provider | API |
|----------|-----|
| `turnstile` | Cloudflare Siteverify |
| `recaptcha` | Google Siteverify |
| `recaptcha-v3` | Google Siteverify |
| `hcaptcha` | hCaptcha Siteverify |
| `friendly-captcha` | Friendly Captcha Siteverify |
| `recaptcha-compatible` | Any compatible siteverify endpoint |
| `captcha-fox` | CaptchaFox-compatible siteverify endpoint |
| `private-captcha` | Private Captcha-compatible siteverify endpoint |
| `prosopo` | Prosopo-compatible siteverify endpoint |

## Error Handling

Provider verification failures are returned as typed results:

```ts
if (!result.success) {
console.log(result.errorCodes);
}
```

Transport/runtime issues throw `CaptchaServerError`:

```ts
import { isCaptchaServerError } from "@better-captcha/server";

try {
// ...
} catch (error) {
if (isCaptchaServerError(error)) {
console.error(error.code, error.status, error.provider);
}
}
```

## API References

- [Cloudflare Turnstile Siteverify](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/)
- [Google reCAPTCHA Verify API](https://developers.google.com/recaptcha/docs/verify)
- [hCaptcha Siteverify API](https://docs.hcaptcha.com/)
- [Friendly Captcha Siteverify](https://developer.friendlycaptcha.com/docs/v2/api/siteverify)
1 change: 1 addition & 0 deletions apps/docs/content/docs/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"[npm][NPM Packages](https://www.npmjs.com/org/better-captcha)",
"---Docs---",
"...",
"server",
"---Frameworks---",
"...frameworks",
"---Provider---",
Expand Down
Loading
Loading