← Back to DocsIntegration Guide

PaywallOS Integration Guide

End-to-end guide: feature gating, Stripe checkout, billing management, and troubleshooting.

1. SDK Infrastructure & Initialization

Environment Variables

NEXT_PUBLIC_PAYWALLOS_API_KEY=<your API key>
NEXT_PUBLIC_PAYWALLOS_APP_ID=<your App UUID>

Global Context

Use a provider to manage tier state and expose testVerb(verb), createCheckout(), and createPortalSession().

CORS

PaywallOS enables CORS on all endpoints. Call directly from the browser. If you hit issues, use a server-side proxy to forward requests to https://paywallos.openverb.org.

2. Feature Gating (Verbs & Tiers)

  • Use checkVerb(verb) or verb="export_data" attributes
  • Create a ProtectedButton that intercepts denied clicks and shows an UpgradeModal
  • Use tier slugs (free, pro), not UUIDs
  • Never let users manually set their tier—all Pro transitions go through Stripe

3. Stripe Checkout (Upgrading)

POST/api/stripe/checkout
{
  "appId": "your-app-uuid",
  "planId": "tier-uuid",
  "userId": "your-user-id",
  "successUrl": "https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}",
  "cancelUrl": "https://yourapp.com/plans"
}

planId is the tier UUID from the dashboard. Include {CHECKOUT_SESSION_ID} in successUrl for post-checkout polling.

4. Post-Checkout Synchronization

Stripe webhooks can take a few seconds. Poll /api/openverb/user-tier every 2–3s until tier is pro (or ~60s max).

async function pollUntilPro(appId, userId, maxAttempts = 20) {
  for (let i = 0; i < maxAttempts; i++) {
    const res = await fetch(
      `https://paywallos.openverb.org/api/openverb/user-tier?appId=${appId}&userId=${userId}`,
      { headers: { Authorization: `Bearer ${apiKey}` } }
    )
    const { tier } = await res.json()
    if (tier === 'pro') return true
    await new Promise(r => setTimeout(r, 3000))
  }
  return false
}

5. Subscription Management (Cancel / Manage)

POST/api/stripe/customer-portal
{
  "appId": "your-app-uuid",
  "userId": "your-user-id",
  "returnUrl": "https://yourapp.com/account"
}

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

6. Stripe Webhook Setup (PaywallOS)

PaywallOS receives webhooks to sync subscriptions. Configure in Stripe Dashboard:

  • 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

7. Database Migration

Run 07-subscriptions-plan-id-flexible.sql if your DB has an FK on subscriptions.plan_id. This allows tier UUIDs and adds cancel_at_period_end.

8. Troubleshooting

IssueSolution
user-tier returns "free" after checkoutConfigure webhook to .../webhooks, set STRIPE_WEBHOOK_SECRET, run migration 07
401 Invalid API keyUse Public Key from Dashboard → Apps → API Keys
Plan not found at checkoutUse tier UUID as planId, not slug. Link Stripe Price to tier.
CORS errorsPaywallOS supports CORS. Use server proxy if needed.
Always deniedVerb name must match exactly. Check tier mapping.