Apps Overview
PayWeave Apps let you add MPP payment gating directly in your code. The SDK is split into a core library and framework-specific adapters. The recommended entry point is payweave.route() — it bundles body validation (Zod or JSON Schema), charging, auto-refund on error, and marketplace manifest registration into a single call.
Architecture
@payweave/core ← PayweaveBase, validation helpers, Zod ↔ JSON Schema
├── @payweave/express ← Express middleware
├── @payweave/fastify ← Fastify preHandler hook
├── @payweave/hono ← Hono middleware (edge-compatible)
└── @payweave/next ← Next.js App Router wrapperCreating an app
From the dashboard, go to Apps → Create App. You'll receive:
appId — public identifier (e.g. app_cm5abc123def456), safe to include in code.
appSecret — secret key, prefixed with sk_test_ or sk_live_. Store it in environment variables.
Installation
npm install @payweave/hono zod # Hono
npm install @payweave/express zod # Express
npm install @payweave/fastify zod # Fastify
npm install @payweave/next zod # Next.jszod is an optional peer dependency. If you'd rather write raw JSON Schema for validation, you can skip it.
The recommended pattern
payweave.route() registers a paid endpoint in one call. It handles:
| Feature | What it does |
|---|---|
| Body validation | Runs Zod safeParse (or JSON Schema) before the handler. Returns 400 on failure. |
| Empty-optional stripping | null/""/[]/undefined on non-required fields are dropped before validation. |
| MPP charging | 402 challenge flow, multi-currency, receipt header — all automatic. |
| Auto-refund on throw | Default ON. Handler throws → caller is refunded with reason = err.message. |
| Marketplace registration | Routes are registered in an in-memory manifest, pushed to the platform on sync. |
Example: the full happy path
import { Hono } from 'hono';
import { Payweave } from '@payweave/hono';
import { z } from 'zod';
const app = new Hono();
const payweave = new Payweave(
process.env.PAYWEAVE_APP_ID!,
process.env.PAYWEAVE_APP_SECRET!
);
const WeatherInput = z.object({ city: z.string() });
payweave.route(app, 'post', '/api/weather', {
price: '0.001',
description: 'Current weather for a city',
inputSchema: WeatherInput,
handler: async (_c, body) => {
// body is typed as { city: string } — inferred from the Zod schema
const res = await fetch(`https://api.weather/?q=${body.city}`);
if (!res.ok) throw new Error('weather_upstream_error');
return await res.json();
},
});
// Mount GET /_payweave/sync so the dashboard's "Sync endpoints" button works.
payweave.mountSync(app);
export default app;Route options
| Option | Type | Description |
|---|---|---|
| price | string | (req) => string | Price per request in USD, e.g. '0.01'. Can be a function for dynamic pricing. |
| description | string | Human-readable endpoint description shown in 402 responses and marketplace. |
| name | string | Short display name for the marketplace listing. Defaults to description. |
| inputSchema | ZodType | JSON Schema | Request body schema. Zod schemas auto-derive the handler body type via z.infer. |
| outputSchema | ZodType | JSON Schema | Response body schema. Published in the generated OpenAPI spec and skill.md. |
| refundOnError | boolean | Default true. On handler throw: refund with reason = err.message, respond 502. Set false to opt out. |
| handler | (ctx, body) => unknown | Your business logic. Return value is JSON-encoded. |
| meta | Record<string, string> | Extra MPP meta entries merged into the 402 challenge. |
Low-level: payweave.charge()
If you need something route() doesn't support (custom auth, pre-charge logic, streaming), use payweave.charge() directly — it returns just the charge middleware without validation or manifest registration. Routes registered this way are not published to the marketplace.
app.get(
'/api/custom',
payweave.charge({ price: '0.001', description: 'Custom flow' }),
(c) => c.json({ ok: true })
);Boot-time config check
Call payweave.assertConfigured(env) early to fail loudly on misconfigured env vars. Recommended in first-request middleware for Cloudflare Workers and at startup for Node-like runtimes.
app.use('*', async (c, next) => {
payweave.assertConfigured(c.env); // throws with a specific error if PAYWEAVE_APP_ID / _APP_SECRET / _API_URL is missing
await next();
});GET /_payweave/sync on your server and pulls the route manifest into the marketplace.