← Back to DocsExternal Integration

Integrating PaywallOS from Your App

A complete guide to connecting your external application to PaywallOS. All endpoints support CORS for browser-based calls.

Overview

PaywallOS exposes a REST API at https://paywallos.openverb.org. You need:

  • API Key — Public key from your app (Dashboard → Apps → API Keys)
  • App ID — UUID of your app (same place)

All endpoints (except health) require: Authorization: Bearer YOUR_API_KEY

1. Health Check

GET/api/openverb/health
Verify connectivity before making real requests. No auth required.

Request

fetch('https://paywallos.openverb.org/api/openverb/health')

Response 200

{
  "status": "ok",
  "message": "PaywallOS Service is active"
}

2. Check Verb Permission (Simple)

GET/api/openverb/check-verb
REST-style check with query params. Use this if you prefer GET over POST.

Query Parameters

  • appId — Your app UUID
  • userId — Your user's unique ID
  • verb — The action (e.g. export_data)
  • tier — User's tier slug (e.g. free, pro), not the tier UUID. Optional, defaults to free.
fetch(
  'https://paywallos.openverb.org/api/openverb/check-verb?appId=YOUR_APP_ID&userId=user_123&verb=export_data&tier=free',
  { headers: { Authorization: 'Bearer YOUR_API_KEY' } }
)

Response 200 (allowed)

{
  "allowed": true,
  "verb": "export_data",
  "tier": "pro",
  "message": "Access granted"
}

Response 403 (denied)

{
  "allowed": false,
  "verb": "export_data",
  "tier": "free",
  "requiredTier": "pro",
  "message": "This feature requires the pro plan"
}

3. Check Verb Permission (POST)

POST/api/openverb/check
Used by the PaywallOS SDK. OpenVerb runtime format with full receipt.

Request Body

{
  "verbId": "export_data",
  "actor": { "type": "user", "id": "user_123" },
  "context": { "tenantId": "YOUR_APP_ID", "planId": "pro" }
}

tenantId = App ID. planId = user's tier slug.

Response 200 (allowed)

{
  "ok": true,
  "receipt": {
    "executionId": "...",
    "verbId": "export_data",
    "status": "ok",
    "actorId": "user_123",
    "tenantId": "YOUR_APP_ID"
  }
}

Response 200 (denied)

{
  "denied": true,
  "reason": {
    "code": "tier_required",
    "message": "Upgrade to Pro"
  },
  "upsell": {
    "suggestedPlanId": "pro",
    "cta": "Upgrade Now"
  }
}

4. Stripe Checkout (Upgrade)

POST/api/stripe/checkout
Create a Stripe Checkout session. Redirect the user to upgrade to Pro.

Request

fetch('https://paywallos.openverb.org/api/stripe/checkout', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    appId: 'YOUR_APP_ID',
    planId: 'tier-uuid',  // Tier UUID from dashboard, not slug
    userId: 'user_123',
    successUrl: 'https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}',
    cancelUrl: 'https://yourapp.com/plans'
  })
})

Response

{ "url": "https://checkout.stripe.com/...", "sessionId": "cs_..." }

Redirect the user to url. Include {CHECKOUT_SESSION_ID} in successUrl so you can poll for tier updates after checkout.

5. Billing Portal (Cancel / Manage)

POST/api/stripe/customer-portal
Get a Stripe Customer Portal URL so users can cancel, update payment, or view invoices.

Request

fetch('https://paywallos.openverb.org/api/stripe/customer-portal', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    appId: 'YOUR_APP_ID',
    userId: 'user_123',
    returnUrl: 'https://yourapp.com/account'
  })
})

Response

{ "url": "https://billing.stripe.com/..." }

Redirect the user to url. Stripe hosts the portal—users can cancel at period end, update payment, view invoices.

6. Fetch User Tier

GET/api/openverb/user-tier
Get the user's tier from PaywallOS (subscription database). Use when you don't manage tiers yourself.

Query Parameters

  • appId — Your app UUID
  • userId — Your user's external ID (must exist as end_user in PaywallOS)
fetch(
  'https://paywallos.openverb.org/api/openverb/user-tier?appId=YOUR_APP_ID&userId=user_123',
  { headers: { Authorization: 'Bearer YOUR_API_KEY' } }
)

Response 200

{
  "userId": "user_123",
  "tier": "pro"
}

Returns tier: "free" if user has no active subscription.

CORS

All OpenVerb API endpoints support CORS for browser-based requests:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Headers: Authorization, Content-Type

You can call these endpoints directly from your frontend (e.g. fetch from React). Keep your API key in an env var (e.g. NEXT_PUBLIC_PAYWALLOS_API_KEY). The public key is safe for client-side use.

Typical Integration Flow

  1. On app load: Call /api/openverb/health to verify connectivity (optional)
  2. When user logs in: Call /api/openverb/user-tier to get their tier, or use your own tier if you manage it
  3. Initialize PaywallOS SDK with initPaywallOS(apiKey, appId, userId, userTier, { baseUrl: 'https://paywallos.openverb.org' })
  4. Add verb="export_data" to buttons/sections that need protection
  5. Or call /api/openverb/check-verb directly for programmatic checks
  6. Upgrade: Call /api/stripe/checkout → redirect to url. Use {CHECKOUT_SESSION_ID} in successUrl.
  7. Post-checkout: Poll /api/openverb/user-tier every 2–3s until tier is pro (webhooks can delay a few seconds)
  8. Cancel/Manage: For Pro users, call /api/stripe/customer-portal → redirect to url

Stripe Webhook Setup (PaywallOS)

PaywallOS receives Stripe webhooks to keep subscriptions in sync. If you run PaywallOS, configure:

  • URL: https://paywallos.openverb.org/api/stripe/webhooks (note: webhooks plural)
  • Events: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.payment_succeeded, invoice.payment_failed
  • Env: STRIPE_WEBHOOK_SECRET in Vercel

If user-tier returns free after checkout, the webhook may not be configured. Run migration 07-subscriptions-plan-id-flexible.sql if needed.