Guides

SDK (any framework)

One middleware file — verivyxProxy (Next), verivyxMiddleware (Express), or verivyxHonoMiddleware (Hono) — gates your whole app. Humans read for free, verified search crawlers get an SEO preview, AI agents pay via x402, and your content never leaves your server unless Verivyx confirms the request is authorised.

Authorize-only model

The SDK does not proxy or cache your content. It inspects every incoming request, checks the caller's identity and payment proof with the Verivyx API, and either lets the request reach your app or answers it itself (a 402 for unpaid agents, a teaser for crawlers, an unlock page for humans). Verivyx only ever sees your site token, the route slug, and the proof-of-payment or verification token — the full resource body stays on your server. Unauthorised callers never reach the route that renders it, so scrapers and bots never receive the content.

Get your site token first

Before adding the SDK, grab a VERIVYX_TOKEN from the dashboard. The token alone identifies your site and authorises the SDK to call the Verivyx API on your behalf — there is no domain entry and no DNS verification step.

The flow: sign up, set your payout wallet and price, then copy your site token and add the middleware. You can re-issue the token any time.

Recommended: one middleware file

The simplest integration is a single middleware that gates every matched route — no per-route code. Install the adapter for your framework and add one file. The middleware is the authoritative gate: it withholds content from bots (they never reach the page), settles agent x402 payments inline, lets verified humans through, and serves crawlers an SEO preview.

Next.js (App Router)

sh
npm i @verivyx/paywall-next

Add a proxy.ts at your project root:

ts
// proxy.ts — one file gates your whole Next.js app.
import { verivyxProxy } from "@verivyx/paywall-next";

export const proxy = verivyxProxy({
  token: process.env.VERIVYX_TOKEN,      // required — your site token (or set VERIVYX_TOKEN env)
  match: ["/articles/:path*"],           // which paths to gate
  seoPreview: ({ slug }) => {            // teaser for crawlers + humans
    const a = getArticleSync(slug);
    return { title: a.title, excerpt: a.excerpt };
  },
  humanUnlock: {},                       // humans solve an in-page PoW → read free
});

export const config = { matcher: ["/((?!_next/|favicon.ico).*)"] };

Express

sh
npm i @verivyx/paywall-express
ts
import express from "express";
import { verivyxMiddleware } from "@verivyx/paywall-express";

const app = express();
app.set("trust proxy", true);

app.use(verivyxMiddleware({
  token: process.env.VERIVYX_TOKEN,   // required — your site token (or set VERIVYX_TOKEN env)
  match: ["/articles/*"],
  seoPreview: ({ slug }) => ({ title: titleFor(slug), excerpt: excerptFor(slug) }),
  humanUnlock: {},
}));

app.get("/articles/:slug", (req, res) => res.send(renderArticle(req.params.slug)));
app.listen(3000);

Hono (Cloudflare Workers / Vercel Edge)

sh
npm i @verivyx/paywall-hono
ts
import { Hono } from "hono";
import { verivyxHonoMiddleware } from "@verivyx/paywall-hono";

const app = new Hono();

app.use("*", verivyxHonoMiddleware({
  token: process.env.VERIVYX_TOKEN,   // required — your site token (or set VERIVYX_TOKEN env)
  match: ["/articles/*"],
  seoPreview: ({ slug }) => ({ title: titleFor(slug), excerpt: excerptFor(slug) }),
  humanUnlock: {},
}));

app.get("/articles/:slug", (c) => c.html(renderArticle(c.req.param("slug"))));
export default app;

For Workers, set the secret with wrangler secret put VERIVYX_TOKEN, then wrangler deploy.

Alternative: per-route handler

Prefer to gate a single API route instead of the whole app? Wrap one handler with vx.protect(handler). Same gate, scoped to that route — handy for a paid JSON endpoint:

ts
import { verivyxNext } from "@verivyx/paywall-next";

const vx = verivyxNext();   // reads VERIVYX_TOKEN from env

// Per-route alternative: wrap a single handler instead of using middleware.
export const GET = vx.protect(
  async (_req, ctx) => Response.json(await getArticle((await ctx.params).slug)),
  { seoPreview: ({ slug }) => ({ title: titleFor(slug), excerpt: excerptFor(slug) }) },
);

The Express and Hono adapters expose the same vx.protect(handler) for a single route.

Humans read for free (humanUnlock)

Set humanUnlock: {} and an unverified human in a real browser gets a small in-page proof-of-work challenge that auto-solves in their browser, sets a short-lived session, and reveals the full article — no payment, no login. Crawlers still get the SEO teaser; non-browser clients and AI agents still get a 402. This is a deterrent against casual scrapers, not a hard wall — the unspoofable guarantees are payment (x402) and signed agents (Web Bot Auth).

Without humanUnlock, unverified humans get the static teaser (the seoPreview) — useful as a soft paywall. With it, they can unlock the full content.

Configuration

The required config comes from environment variables (or pass any option to the factory):

sh
VERIVYX_TOKEN=…              # required — server-only secret, never ship to the browser
VERIVYX_API_BASE=https://api.verivyx.com   # optional (this is the default)
VERIVYX_DOMAIN=example.com   # optional — legacy/analytics label only; the token identifies your site
OptionTypeDefaultDescription
token / VERIVYX_TOKENstringRequired. Your site token from the dashboard — it alone identifies your site. Server-only — never expose to the browser.
domain / VERIVYX_DOMAINstring""Optional. Legacy/analytics label only (e.g. example.com); not required and not part of onboarding — the token identifies your site.
matchstring[][]Glob patterns for paths to gate. When empty, nothing is gated — set at least one. Env VERIVYX_MATCH accepts a comma-separated list.
seoPreview({ slug }) => { title, excerpt }Teaser served to crawlers (and to unverified humans without humanUnlock), wrapped in anti-cloaking JSON-LD.
humanUnlock{ authBase? }When set, unverified human browsers get an in-page PoW unlock to read the full content free.
failMode / VERIVYX_FAIL_MODEteaser | open | closedteaserBehaviour when the Verivyx backend is unreachable (see below).
timeoutMs / VERIVYX_TIMEOUT_MSnumber800Timeout (ms) for the quick classify/requirements call that decides how a caller is handled.
settleTimeoutMs / VERIVYX_SETTLE_TIMEOUT_MSnumber60000Timeout (ms) for the authorize/settle call that awaits the on-chain payment. Kept separate so a paying agent is never aborted mid-settle.
Two timeouts, by design. timeoutMs (default 800) bounds the fast classify call for humans and crawlers, while settleTimeoutMs (default 60000) covers the x402 authorize/settle path that waits for on-chain confirmation (~15s). Because the settle path has its own generous timeout, you do not need to raise timeoutMs when you accept agent payments — a paying agent is no longer aborted mid-settle.

failMode behaviour

failModeBehaviour when the backend is unreachable
teaserServe the seoPreview (if configured) or a 402. Protects revenue while keeping the page indexable.
openPass the request through to your app unconditionally. Use only when availability outweighs monetisation.
closedReturn 503. Use for high-value content where accidental open access is unacceptable.

How different callers are handled

  • Humans (real browsers) — with humanUnlock, they get an in-page PoW unlock → full content free. Without it, they get the seoPreview teaser. A returning human with a valid session passes straight through.
  • Verified search crawlers— Googlebot, Bingbot, and others on Verivyx's IP-range allowlist receive the seoPreview (title, excerpt, JSON-LD isAccessibleForFree) rather than the full body — satisfying Google's anti-cloaking rules.
  • AI agents / machine clients — receive 402 Payment Required with x402 payment requirements. Agents that implement x402 settle a USDC micropayment on-chain and retry; the SDK verifies the proof and admits the request.
  • Paid / verified requests — requests carrying a valid payment proof or human session receive the full resource, exactly as if the SDK were not there.

Security note

Keep VERIVYX_TOKEN server-only. Never include it in client bundles, expose it via NEXT_PUBLIC_, or log it. It is a bearer credential that identifies your site. If it leaks, re-issue it from the dashboard — old tokens are invalidated the moment a new one is issued.