Node.js SDK
The Orvion Node.js SDK provides seamless integration with Express for creating payment-protected API endpoints using the x402 protocol.
Quick Start
1. Install the SDK
npm install @orvion/sdk
2. Initialize Orvion
import express from 'express'import { orvionInit } from '@orvion/sdk/express'const app = express()// Initialize OrvionorvionInit({apiKey: process.env.ORVION_API_KEY!})
3. Protect Endpoints
import { requirePayment } from '@orvion/sdk/express'// Define a protected endpoint with pricingapp.get('/api/premium/data',requirePayment({ amount: '0.10', currency: 'USDC' }),(req, res) => {res.json({data: 'Premium content!',paid: req.payment.amount})})
Pre-built Payment Router (NEW in v0.2)
Eliminate boilerplate by using the pre-built payment router:
import { OrvionClient } from '@orvion/sdk'import { createPaymentRouter } from '@orvion/sdk/express'const client = new OrvionClient({ apiKey: process.env.ORVION_API_KEY! })// Add all payment endpoints with one line!app.use('/api/payments', createPaymentRouter(client))// This adds:// POST /api/payments/confirm - Confirm wallet payment// POST /api/payments/cancel/:id - Cancel pending charge// GET /api/payments/state/:id - Get charge state for UI// GET /api/payments/charge/:id - Get full charge details
orvionInit
Initialize the Orvion SDK with your API key and configuration.
Configuration
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
| apiKey | string | Optional | Your Orvion API key (required) | — |
| baseUrl | string? | Optional | Custom API base URL (default: https://api.orvion.sh) | — |
| cacheTtlSeconds | number | Optional | Route cache TTL in seconds (default: 60) | — |
| transactionHeader | string | Optional | Header for transaction ID (default: 'X-Transaction-Id') | — |
| customerHeader | string | Optional | Header for customer ID (default: 'X-Customer-Id') | — |
requirePayment Middleware
Protect Express routes with payment requirements.
Parameters
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
| amount | string? | Optional | Price per request (e.g., '0.10') | — |
| currency | string | Optional | Currency code (default: 'USD') | — |
| allowAnonymous | boolean? | Optional | Allow requests without customer ID (default: true) | — |
| customerResolver | Function? | Optional | Function to extract customer ID from request | — |
| hostedCheckout | boolean | Optional | Enable hosted checkout mode - redirects to pay.orvion.sh (default: false) | — |
| returnUrl | string? | Optional | Return URL for hosted checkout. Auto-derived using convention: /api/foo → /foo | — |
Examples
// Basic usageapp.get('/api/basic',requirePayment({ amount: '0.01', currency: 'USDC' }),(req, res) => {res.json({ data: '...' })})// With metadataapp.get('/api/premium',requirePayment({amount: '1.00',currency: 'USDC',}),(req, res) => {res.json({ data: '...', paid: req.payment.amount })})// Require customer identificationapp.get('/api/user-data',requirePayment({amount: '0.05',currency: 'USDC',allowAnonymous: false,customerResolver: (req) => req.headers['x-user-id'],}),(req, res) => {res.json({ user: req.payment.customerRef })})// Hosted checkout mode - redirects to pay.orvion.shapp.get('/premium',requirePayment({amount: '1.00',currency: 'USDC',hostedCheckout: true,}),(req, res) => {res.json({ message: 'Premium content after hosted checkout!' })})
New Client Methods (v0.2)
The SDK now includes powerful client methods for payment operations:
import { OrvionClient } from '@orvion/sdk'const client = new OrvionClient({ apiKey: 'your-api-key' })// Health check & API key validationconst health = await client.healthCheck()console.log(`Organization: ${health.organizationId}`)// Create a chargeconst charge = await client.createCharge({amount: '0.10',currency: 'USDC',})// Get charge state (optimized for UI widgets)const state = await client.getChargeState(charge.id)console.log(`Status: ${state.status}`)// Confirm wallet paymentconst result = await client.confirmPayment({transactionId: charge.id,txHash: 'blockchain_tx_signature...',})// Cancel a pending chargeconst cancelled = await client.cancelCharge(charge.id)
Accessing Payment Info
After successful payment verification, payment details are available on req.payment:
app.get('/api/premium',requirePayment({ amount: '0.10', currency: 'USDC' }),(req, res) => {const { payment } = reqres.json({transactionId: payment.transactionId,amount: payment.amount,currency: payment.currency,customerRef: payment.customerRef,})})
Routing Flows vs Standalone Charges
Each protected route has its own amount stored in the protected_routes table. When a route is accessed, Orvion determines how to process the payment:
With Routing Flows
If you have an active routing flow with an api_request_entry node, the route automatically uses that flow for payment configuration:
- Amount: Uses the route's amount from
protected_routestable - Payment Config: Network, asset, and receiver come from the routing flow
- Use Case: Dynamic routing, conditional logic, A/B testing
// Route with routing flowapp.get('/api/flow',requirePayment({amount: '0.0015', // Route's own amountcurrency: 'USDC',}),(req, res) => {// Payment config comes from active routing flow// Amount comes from this route definitionres.json({ message: 'Routed through flow!' })})
Standalone Charges
If there's no active routing flow, the route uses a standalone charge:
- Amount: Uses the route's amount from
protected_routestable - Payment Config: Uses the route's
receiverConfigId(or default receiver) - Use Case: Simple, fixed pricing with direct control
// Route with standalone chargeapp.get('/api/premium',requirePayment({amount: '0.001', // Route's own amountcurrency: 'USDC',receiverConfigId: 'config_abc123', // Optional: specific receiver config}),(req, res) => {// Payment config comes from receiverConfigId or default// Amount comes from this route definitionres.json({ message: 'Premium content!' })})
Key Point: Every protected route stores its own amount. The difference between routing flows and standalone charges is only in how the payment configuration (network, asset, receiver) is determined—not the amount itself.
Complete Example
import express from 'express'import { OrvionClient } from '@orvion/sdk'import {orvionInit,requirePayment,createPaymentRouter,} from '@orvion/sdk/express'const app = express()app.use(express.json())const client = new OrvionClient({ apiKey: process.env.ORVION_API_KEY! })// Initialize OrvionorvionInit({ apiKey: process.env.ORVION_API_KEY! })// Add pre-built payment routerapp.use('/api/payments', createPaymentRouter(client))// Free endpointapp.get('/', (req, res) => {res.json({ message: 'Welcome! Visit /api/premium for paid content.' })})// Premium endpoint - 0.01 USDCapp.get('/api/premium',requirePayment({amount: '0.01',currency: 'USDC',}),(req, res) => {res.json({message: 'Welcome to premium!',paid: req.payment.amount,})})app.listen(3000, () => {console.log('Server running on http://localhost:3000')})
TypeScript Types
import type { PaymentInfo } from '@orvion/sdk/express'interface RequestWithPayment extends express.Request {payment: PaymentInfo}app.get('/api/premium',requirePayment({ amount: '0.10', currency: 'USDC' }),(req: RequestWithPayment, res) => {// TypeScript knows about req.paymentconst transactionId: string = req.payment.transactionIdconst amount: string = req.payment.amountres.json({ transactionId, amount })})
Hosted Checkout Mode
For web applications where users interact via browser, use hosted checkout mode:
// API endpoint - protected with paymentapp.get('/api/premium',requirePayment({amount: '1.00',currency: 'USDC',hostedCheckout: true, // Redirect to pay.orvion.sh}),(req, res) => {res.json({ message: 'Premium content!' })})// Frontend page - serves the UIapp.get('/premium', (req, res) => {res.sendFile('static/premium.html')})
Automatic URL Convention
The SDK uses a convention-based approach for seamless UX:
- API at
/api/premium→ Frontend at/premium - The
/apiprefix is automatically stripped - Users are redirected to the frontend page after payment (not the API)
No returnUrl configuration needed! Just add hostedCheckout: true and follow the convention.
// That's all you need! No returnUrl required.app.get('/api/premium',requirePayment({ amount: '1.00', currency: 'USDC', hostedCheckout: true }),(req, res) => {res.json({ data: 'premium' })})// Flow:// 1. User on /premium clicks "Pay" → redirects to /api/premium// 2. SDK creates charge, redirects to pay.orvion.sh// 3. After payment, user returns to /api/premium?charge_id=xxx// 4. SDK verifies, then redirects to /premium?charge_id=xxx&status=succeeded// 5. Frontend shows success message
Before using hosted checkout, configure allowed domains in Settings → Domains to enable return URL validation.
See the Hosted Checkout Guide for complete documentation.
Related Documentation
- Protected Routes Overview - General protected routes guide
- Hosted Checkout - Redirect-based payment flow for web apps
- Node.js SDK - Full Node.js SDK reference
- API Reference - API endpoints and schemas