Apps

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

Plain Text
@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 wrapper

Creating 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

Terminal
npm install @payweave/hono zod         # Hono
npm install @payweave/express zod      # Express
npm install @payweave/fastify zod      # Fastify
npm install @payweave/next zod         # Next.js

zod is an optional peer dependency. If you'd rather write raw JSON Schema for validation, you can skip it.

payweave.route() registers a paid endpoint in one call. It handles:

FeatureWhat it does
Body validationRuns Zod safeParse (or JSON Schema) before the handler. Returns 400 on failure.
Empty-optional strippingnull/""/[]/undefined on non-required fields are dropped before validation.
MPP charging402 challenge flow, multi-currency, receipt header — all automatic.
Auto-refund on throwDefault ON. Handler throws → caller is refunded with reason = err.message.
Marketplace registrationRoutes are registered in an in-memory manifest, pushed to the platform on sync.

Example: the full happy path

TypeScript
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

OptionTypeDescription
pricestring | (req) => stringPrice per request in USD, e.g. '0.01'. Can be a function for dynamic pricing.
descriptionstringHuman-readable endpoint description shown in 402 responses and marketplace.
namestringShort display name for the marketplace listing. Defaults to description.
inputSchemaZodType | JSON SchemaRequest body schema. Zod schemas auto-derive the handler body type via z.infer.
outputSchemaZodType | JSON SchemaResponse body schema. Published in the generated OpenAPI spec and skill.md.
refundOnErrorbooleanDefault true. On handler throw: refund with reason = err.message, respond 502. Set false to opt out.
handler(ctx, body) => unknownYour business logic. Return value is JSON-encoded.
metaRecord<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.

TypeScript
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.

TypeScript
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();
});
After deploying, set the app's domain in dashboard Settings, then click Sync endpoints on the Endpoints tab. The platform calls GET /_payweave/sync on your server and pulls the route manifest into the marketplace.
Never expose secret keys in client-side code, public repositories, or logs. The secret is only shown once at creation. Rotate from Settings if compromised — secrets are hashed at rest.