Add a Vlexivo Paywall in 10 Minutes
A step-by-step walkthrough from zero to a working HTTP 402 paywall on your Express app. Covers API billing, content paywalls, and AI agent access.
How Vlexivo Works
Vlexivo uses the standard HTTP 402 "Payment Required" status to protect resources. The flow is three steps:
When a consumer hits a paid route without an entitlement token, your middleware calls POST /v1/challenge to get a signed challenge, then returns HTTP 402 with the challenge in the response body.
The consumer (browser wallet, AI agent, or mobile app) sees the 402, sends crypto to the publisher's settlement wallet, and submits the transaction hash as proof.
Vlexivo verifies the on-chain transaction at POST /v1/unlock and returns an entitlement JWT. The consumer re-sends the request with the JWT — your middleware lets them through.
Prerequisites
Before you start, make sure you have:
- Node.js 18+ — Vlexivo requires modern Node.js.
- A Vlexivo publisher account — Register here. Free to start, no credit card.
- Your API key pair — From your dashboard: a
vlx_pub_...(publishable, safe for clients) andvlx_sec_...(secret, server-side only). - A settlement wallet — Configure your Ethereum/Base or Solana wallet address in Dashboard → Settings. This is where consumer payments land.
- An existing Express.js app — Or use the minimal skeleton below.
You don't need your own blockchain node. Vlexivo handles on-chain verification against Ethereum mainnet, Base L2, and Solana mainnet on your behalf.
Install
Option A — npm package
The official Vlexivo Node.js SDK handles challenge generation and entitlement verification for you:
npm install vlexivo
Option B — Script tag (browser widget)
For content paywalls, embed the Vlexivo widget directly in your HTML. No npm required.
<script
src="https://vlexivo.online/sdk/vlx-widget.js"
data-publisher-id="YOUR_PUBLISHER_ID"
async
></script>
Option C — Raw HTTP (no SDK)
Vlexivo's protocol is plain HTTP/JSON. You can integrate with any language using fetch or curl. The examples in this guide show both SDK and raw HTTP.
Never expose your vlx_sec_... secret key on the client side or in public repos. Use environment variables (process.env.VLEXIVO_SECRET_KEY) for all secret key references.
Basic Integration — 5 Minutes
This minimal example protects a single Express route behind a micropayment. Copy-paste, fill in your keys, and you're live.
Step 1 — Create a pricing rule
Before your server can generate challenges, it needs a pricing rule for the resource. Create one via the API (or in your Dashboard → Pricing):
curl -X POST https://vlexivo.online/api/pricing \
-H "X-Api-Key: vlx_sec_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"scope_type": "per-call",
"resource_pattern": "/api/premium/data",
"price_amount": "0.001",
"price_currency": "USDC"
}'
The resource_pattern is matched against resource_id values in challenge requests. Use an exact path for per-route pricing, or * for a catch-all.
Step 2 — Add the paywall middleware
Your Express middleware intercepts requests, checks for an entitlement JWT, and returns 402 if none is present:
const fetch = require('node-fetch'); // or use built-in fetch (Node 18+)
const VLEXIVO_API = 'https://vlexivo.online';
const SECRET_KEY = process.env.VLEXIVO_SECRET_KEY; // vlx_sec_...
/**
* Vlexivo paywall middleware.
* Protects a route behind a micropayment.
*
* Usage:
* app.get('/api/premium/data', requirePayment('/api/premium/data'), handler);
*/
function requirePayment(resourceId) {
return async function vlexivoPaywall(req, res, next) {
// 1. Check for existing entitlement token
const token =
req.headers['x-entitlement'] ||
(req.headers.authorization || '').replace('Bearer ', '');
if (token) {
// 2. Validate the token with Vlexivo
const validation = await fetch(`${VLEXIVO_API}/api/entitlements/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': SECRET_KEY,
},
body: JSON.stringify({ token, resource_id: resourceId }),
}).then(r => r.json());
if (validation.valid) {
req.entitlement = validation.entitlement;
return next(); // ✅ Paid — proceed to route handler
}
}
// 3. No valid token — issue a 402 challenge
const challengeRes = await fetch(`${VLEXIVO_API}/v1/challenge`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': SECRET_KEY,
},
body: JSON.stringify({
resource_id: resourceId,
scope_type: 'per-call',
}),
});
const challenge = await challengeRes.json();
// 4. Return 402 with the challenge — consumer must pay, then retry
return res.status(402).json({
error: 'Payment required',
payment_required: true,
challenge,
});
};
}
module.exports = { requirePayment };
Step 3 — Wire up the route
const express = require('express');
const { requirePayment } = require('./middleware/paywall');
const app = express();
app.use(express.json());
// ✅ Protected route — requires 0.001 USDC per call
app.get(
'/api/premium/data',
requirePayment('/api/premium/data'),
(req, res) => {
// Only reached after valid payment
res.json({
data: 'Your premium content here',
paid_by: req.entitlement?.buyer_wallet,
});
}
);
app.listen(3000, () => console.log('Server running on :3000'));
Step 4 — Test the flow
# Should return 402 with challenge body
curl -i http://localhost:3000/api/premium/data
# After payment, pass the JWT in the Authorization header
curl -i http://localhost:3000/api/premium/data \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
That's the full integration. The three lines that matter: call /v1/challenge, return HTTP 402, validate the JWT on retry. Everything else is ergonomics.
Content Paywall Example
Protect a blog post or article behind a micropayment. This example uses scope_type: "per-article" — the reader pays once per article and gets 24-hour access.
Create a per-article pricing rule
curl -X POST https://vlexivo.online/api/pricing \
-H "X-Api-Key: vlx_sec_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"scope_type": "per-article",
"resource_pattern": "*",
"price_amount": "0.05",
"price_currency": "USDC",
"duration_seconds": 86400
}'
Express route with article paywall
const express = require('express');
const fetch = require('node-fetch');
const router = express.Router();
const VLEXIVO_API = 'https://vlexivo.online';
const SECRET_KEY = process.env.VLEXIVO_SECRET_KEY;
router.get('/articles/:slug', async (req, res) => {
const { slug } = req.params;
const resourceId = `/articles/${slug}`;
const article = await db.getArticle(slug); // your DB call
if (!article) return res.status(404).json({ error: 'Not found' });
// --- Check for entitlement token ---
const token = req.headers['x-entitlement'] ||
(req.headers.authorization || '').replace('Bearer ', '');
if (token) {
const validation = await fetch(`${VLEXIVO_API}/api/entitlements/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Api-Key': SECRET_KEY },
body: JSON.stringify({ token, resource_id: resourceId }),
}).then(r => r.json());
if (validation.valid) {
// Full article — paid access
return res.json({
title: article.title,
content: article.full_content, // complete article body
paid: true,
});
}
}
// --- No valid token: return preview + 402 challenge ---
const challenge = await fetch(`${VLEXIVO_API}/v1/challenge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Api-Key': SECRET_KEY },
body: JSON.stringify({
resource_id: resourceId,
scope_type: 'per-article',
}),
}).then(r => r.json());
return res.status(402).json({
title: article.title,
preview: article.excerpt, // first 200 words shown free
paid: false,
payment_required: true,
challenge, // pass to client SDK to trigger payment UI
});
});
module.exports = router;
Embed the widget in your article template
The Vlexivo widget automatically detects 402 responses and presents the payment UI to the reader:
<!-- Embed widget in your article template -->
<script
src="https://vlexivo.online/sdk/vlx-widget.js"
data-publisher-id="YOUR_PUBLISHER_ID"
async
></script>
<!-- Gate the full article body -->
<div data-vlx-gate="article-full">
<!-- Full content loaded after payment -->
</div>
<!-- Trigger paywall button -->
<button
data-vlx-price="0.05"
data-vlx-currency="USDC"
data-vlx-scope="per-article"
data-vlx-resource="/articles/my-slug"
>
Read full article — $0.05
</button>
The widget handles the entire consumer flow: MetaMask/Phantom popup, payment confirmation, and entitlement storage in localStorage. The reader pays once and gets 24 hours of free access on return visits.
API Billing Example
Charge per API call — the primary use case for Vlexivo. Every request to your API costs a small amount; the caller pays automatically using their Vlexivo entitlement or agent key.
Full middleware pattern for API billing
const fetch = require('node-fetch');
const VLEXIVO_API = 'https://vlexivo.online';
const SECRET_KEY = process.env.VLEXIVO_SECRET_KEY;
/**
* Full Vlexivo API billing middleware.
*
* 1. If X-Entitlement or Authorization: Bearer is present, validate it.
* 2. If valid — pass through (consume 1 entitlement use).
* 3. If missing or invalid — call POST /v1/challenge, return 402.
*
* Consumers use the Vlexivo SDK or agent key to pay automatically.
*/
async function vlexivoBilling(req, res, next) {
const resourceId = req.path;
const token =
req.headers['x-entitlement'] ||
(req.headers.authorization || '').replace('Bearer ', '').trim();
if (token) {
let valid = false;
try {
const result = await fetch(`${VLEXIVO_API}/api/entitlements/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': SECRET_KEY,
},
body: JSON.stringify({ token, resource_id: resourceId }),
}).then(r => r.json());
if (result.valid) {
req.entitlement = result.entitlement;
valid = true;
}
} catch (err) {
console.error('[Vlexivo] Validation error:', err.message);
}
if (valid) return next();
}
// No valid entitlement — issue 402 challenge
try {
const challengeRes = await fetch(`${VLEXIVO_API}/v1/challenge`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': SECRET_KEY,
},
body: JSON.stringify({
resource_id: resourceId,
scope_type: 'per-call',
// Optional: override price inline (no pricing rule needed)
// price: { amount: '0.001', currency: 'USDC' },
}),
});
const challenge = await challengeRes.json();
res.setHeader('X-Vlexivo-Challenge-Nonce', challenge.challenge_nonce || '');
return res.status(402).json({
error: 'Payment required',
code: 'PAYMENT_REQUIRED',
challenge,
unlock_url: `${VLEXIVO_API}/v1/unlock`,
});
} catch (err) {
console.error('[Vlexivo] Challenge generation failed:', err.message);
// Fail open — don't block the request if Vlexivo is unreachable
return next();
}
}
module.exports = { vlexivoBilling };
Apply to all premium routes
const express = require('express');
const { vlexivoBilling } = require('./middleware/vlexivoMiddleware');
const app = express();
app.use(express.json());
// Apply billing to all /api/premium/* routes
app.use('/api/premium', vlexivoBilling);
app.get('/api/premium/weather', (req, res) => {
res.json({ temperature: 72, condition: 'Sunny' });
});
app.get('/api/premium/sentiment', (req, res) => {
const text = req.query.text || '';
res.json({ sentiment: 'positive', score: 0.87, text });
});
// Free tier — no billing middleware
app.get('/api/free/ping', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(3000);
Response format
Your 402 response body should always include the challenge object. This is what SDK consumers and AI agents parse to initiate payment:
{
"error": "Payment required",
"code": "PAYMENT_REQUIRED",
"challenge": {
"challenge_nonce": "vlx_nonce_abc123...",
"publisher_id": 42,
"resource_id": "/api/premium/weather",
"scope_type": "per-call",
"price": { "amount": "0.001", "currency": "USDC" },
"payment_address": "0xYourSettlementWallet",
"unlock_url": "https://vlexivo.online/v1/unlock",
"expires_at": "2026-04-29T12:00:00.000Z",
"accept_currencies": ["USDC", "ETH", "SOL"]
},
"unlock_url": "https://vlexivo.online/v1/unlock"
}
The challenge_nonce is single-use and expires in 5 minutes. If the consumer doesn't pay within that window, they need to request a fresh challenge by hitting your endpoint again.
AI Agent Integration
AI agents don't have browser wallets. Vlexivo provides a dedicated machine-to-machine endpoint — POST /v1/agent/pay — that deducts from a prepaid balance and issues an entitlement JWT in a single round-trip.
How agents access paid APIs
Via Dashboard → Agent Keys. Fund the key's balance with USDC. Set rate limits and auto-topup threshold.
POST /v1/agent/pay
One call, no blockchain interaction. Vlexivo deducts the price from the agent's prepaid balance and returns a JWT.
Passes the entitlement token as Authorization: Bearer <token> or X-Entitlement: <token>. Your existing middleware validates it.
Agent pay — complete code example
const fetch = require('node-fetch');
const VLEXIVO_API = 'https://vlexivo.online';
const AGENT_KEY = process.env.VLEXIVO_AGENT_KEY; // vlxa_live_...
const PUBLISHER_KEY = process.env.TARGET_PUBLISHER_KEY; // Publisher's vlx_sec_...
/**
* Pay for one API call and get an entitlement JWT.
* Deducts from prepaid agent balance — no wallet interaction required.
*/
async function payForAccess(resourceId = '*', scopeType = 'per-call') {
const res = await fetch(`${VLEXIVO_API}/v1/agent/pay`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Agent-Key': AGENT_KEY,
},
body: JSON.stringify({
resource_id: resourceId,
scope_type: scopeType,
publisher_api_key: PUBLISHER_KEY,
}),
});
const data = await res.json();
if (!res.ok || !data.success) {
if (res.status === 402) {
throw new Error(`Insufficient balance: $${data.balance_usd} available. Top up at ${data.topup_url}`);
}
if (res.status === 429) {
throw new Error(`Rate limit exceeded. Retry after ${data.retry_after}s`);
}
throw new Error(`Payment failed: ${data.error}`);
}
return {
token: data.entitlement, // JWT — pass as Authorization: Bearer
expiresAt: data.expires_at,
costUsd: data.cost_usd,
balanceCents: data.balance_cents,
};
}
/**
* Access a paid API endpoint as an AI agent.
*/
async function callPaidApi(endpoint, resourceId) {
// 1. Pay first
const { token, costUsd, balanceCents } = await payForAccess(resourceId);
console.log(`Paid $${costUsd} | Remaining balance: $${(balanceCents / 100).toFixed(4)}`);
// 2. Call the API with the entitlement token
const res = await fetch(endpoint, {
headers: {
'Authorization': `Bearer ${token}`,
// Or: 'X-Entitlement': token
},
});
if (!res.ok) {
throw new Error(`API call failed: ${res.status} ${await res.text()}`);
}
return res.json();
}
// Example usage
(async () => {
const data = await callPaidApi(
'https://your-api.example.com/api/premium/weather',
'/api/premium/weather'
);
console.log('Weather data:', data);
})();
Agent pay — response shape
{
"success": true,
"entitlement": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-04-29T12:05:00.000Z",
"cost_micro_usd": 1000,
"cost_usd": "0.001000",
"balance_cents": 4999,
"balance_usd": "49.9900",
"resource_id": "/api/premium/weather",
"scope_type": "per-call",
"latency_ms": 12
}
Check agent balance
curl https://vlexivo.online/v1/agent/status \
-H "X-Agent-Key: vlxa_live_YOUR_AGENT_KEY"
Rate limits for agents
| Limit | Default | Configurable |
|---|---|---|
| Requests per minute | 60 | Yes — per agent key |
| Requests per day | 10,000 | Yes — per agent key |
| Response on exceeded | HTTP 429 with Retry-After header | |
Testing Without Real Payments
Set DEMO_MODE=true in your environment. In demo mode, Vlexivo's verification engine accepts any well-formed proof without performing on-chain checks. You can test the entire flow locally without spending crypto.
Local .env setup
VLEXIVO_SECRET_KEY=vlx_sec_YOUR_SECRET_KEY
VLEXIVO_PUBLISHABLE_KEY=vlx_pub_YOUR_PUBLISHABLE_KEY
DEMO_MODE=true # <-- skip on-chain verification
Submit a stub proof in demo mode
In demo mode, you can call POST /v1/unlock with a fake transaction hash and it will succeed:
# 1. Get a challenge
CHALLENGE=$(curl -s -X POST https://vlexivo.online/v1/challenge \
-H "X-Api-Key: vlx_sec_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"resource_id": "/api/premium/data", "scope_type": "per-call"}')
NONCE=$(echo $CHALLENGE | jq -r '.challenge_nonce')
echo "Nonce: $NONCE"
# 2. Submit a stub proof (demo mode accepts any well-formed proof)
TOKEN=$(curl -s -X POST https://vlexivo.online/v1/unlock \
-H "Content-Type: application/json" \
-d "{
\"proof\": {
\"tx_hash\": \"0xdeadbeef000000000000000000000000000000000000000000000000deadbeef\",
\"buyer_wallet\": \"0x1234567890abcdef1234567890abcdef12345678\",
\"nonce\": \"$NONCE\"
},
\"currency\": \"USDC\"
}" | jq -r '.entitlement_token')
echo "Token: $TOKEN"
# 3. Access the protected route
curl -i http://localhost:3000/api/premium/data \
-H "Authorization: Bearer $TOKEN"
Never deploy with DEMO_MODE=true. In demo mode, anyone can unlock any resource with a fake proof. Set DEMO_MODE=false (or remove the env var) before going live.
Unit testing your middleware
const request = require('supertest');
const nock = require('nock');
const app = require('../server');
describe('Vlexivo paywall middleware', () => {
test('returns 402 when no entitlement token is present', async () => {
// Mock the Vlexivo challenge endpoint
nock('https://vlexivo.online')
.post('/v1/challenge')
.reply(200, {
challenge_nonce: 'vlx_nonce_test123',
price: { amount: '0.001', currency: 'USDC' },
payment_address: '0xabc',
unlock_url: 'https://vlexivo.online/v1/unlock',
});
const res = await request(app).get('/api/premium/data');
expect(res.status).toBe(402);
expect(res.body.challenge).toBeDefined();
expect(res.body.challenge.challenge_nonce).toBe('vlx_nonce_test123');
});
test('passes through with a valid entitlement token', async () => {
nock('https://vlexivo.online')
.post('/api/entitlements/validate')
.reply(200, {
valid: true,
entitlement: { buyer_wallet: '0xabc', scope_type: 'per-call' },
});
const res = await request(app)
.get('/api/premium/data')
.set('X-Entitlement', 'mock-jwt-token');
expect(res.status).toBe(200);
expect(res.body.data).toBeDefined();
});
});
Going Live Checklist
Before switching production traffic to your Vlexivo integration, run through this checklist:
-
Remove DEMO_MODE — Unset
DEMO_MODEor set tofalse. On-chain verification is active in production. - Set real prices — Review your pricing rules in Dashboard → Pricing. Make sure amounts are correct for your use case ($0.001 is typical for per-call APIs).
- Configure settlement wallet — Verify your Ethereum/Base or Solana wallet address in Dashboard → Settings. This is where revenue lands.
-
Secret key is in env vars —
VLEXIVO_SECRET_KEYmust never appear in code, git history, or logs. - Test with a real micro-payment — Send 0.001 USDC on Base L2 through the actual flow before opening to users.
-
Set up webhooks — Register a
payment.completedwebhook in Dashboard → Webhooks. Use this for fulfillment, analytics, and anomaly detection. -
Monitor via analytics — Visit
/admin/analyticsor Dashboard → Analytics for 7-day event breakdown. Set up alerts for unusual drop-off rates. - Handle errors gracefully — Your middleware should fail open (let requests through) if Vlexivo is unreachable, not fail closed. Log the error, don't 500.
Monitoring
Track payment funnel health with these API endpoints:
# 7-day analytics breakdown (challenge, unlock, agent_pay events)
curl https://vlexivo.online/admin/analytics \
-H "X-Api-Key: vlx_sec_YOUR_KEY"
# Earnings summary (time-windowed)
curl "https://vlexivo.online/api/analytics/earnings?window=7d" \
-H "X-Api-Key: vlx_sec_YOUR_KEY"
# Recent payments list (with filters)
curl "https://vlexivo.online/api/payments?scope_type=per-call&date_from=2026-04-01" \
-H "X-Api-Key: vlx_sec_YOUR_KEY"
Troubleshooting
| Error Code | HTTP | Cause & Fix |
|---|---|---|
| NO_PRICING_RULE | 400 | No pricing rule matches your resource_id. Create one via POST /api/pricing or pass an inline price in the challenge request. |
| MISSING_RESOURCE_ID | 400 | The resource_id field is missing or empty in your challenge request body. |
| INVALID_PROOF | 400 | Malformed proof body sent to POST /v1/unlock. Check that tx_hash, buyer_wallet, and nonce are all present and correctly formatted. |
| NONCE_NOT_FOUND | 404 | The nonce was never issued or was cleaned up (older than 5 minutes). Request a fresh challenge. |
| NONCE_REPLAY | 409 | This nonce has already been used. Each nonce is single-use. Request a new challenge to retry. |
| CHALLENGE_EXPIRED | 410 | Challenge expired (5 minute TTL). Request a fresh challenge and submit payment within the window. |
| Payment verification failed | 402 | On-chain verification failed. Common causes: tx not confirmed yet (wait 15s, retry), wrong recipient wallet, amount too low. Use DEMO_MODE=true to bypass for testing. |
| INSUFFICIENT_BALANCE | 402 | Agent key has insufficient prepaid balance. Top up at Dashboard → Agent Keys or enable auto-topup. |
| RATE_LIMIT_EXCEEDED | 429 | Rate limit hit. Check the Retry-After header. Default limits: 60 req/min for /v1/challenge, 30 req/min for /v1/unlock. Contact us for higher limits. |
| AGE_PROOF_REQUIRED | 403 | Publisher requires age verification. Consumer must set up an age credential at /consumer/dashboard and include age_proof in the unlock request. |
Common integration issues
The payment popup doesn't appear
Ensure your page doesn't block popups. The Vlexivo widget opens in a window.open popup. On iOS Safari, popups are only allowed from direct user gesture handlers. Make sure the payment button click handler calls the SDK synchronously — don't await anything before calling window.open.
Entitlement token expires too fast
Default TTL for per-call is 300 seconds (5 min). For per-article it's 24 hours. Extend via the duration_seconds field on your pricing rule. For sessions, use scope_type: "per-session" and set your own duration.
On-chain verification times out
Vlexivo polls the blockchain for up to 30 seconds after receiving a proof. If the tx confirms after that window, use the retry endpoint:
curl -X POST https://vlexivo.online/v1/unlock/retry \
-H "Content-Type: application/json" \
-d '{"nonce": "vlx_nonce_...", "tx_hash": "0x..."}'
Webhook keeps failing
Vlexivo retries with exponential backoff: 1m → 5m → 30m → 2h → 24h. To debug, check Dashboard → Webhooks → Deliveries. Common fixes: return 200 immediately (process async), fix signature verification (X-Vlexivo-Signature header), ensure endpoint is reachable from the internet.
Still stuck?
Email support@vlexivo.online with your publisher ID, the exact endpoint you're calling, and the full error response body. We respond within 24 hours on weekdays.
For full endpoint reference, error codes, and SDK options — see the API Reference →
Ready to monetize?
Create your free publisher account and add a paywall in minutes. No credit card required.