LedeWire SDK

Browser CDN — Getting Started

Add a paywall to any webpage without a build step. Drop in a single <script> tag and the Ledewire global is ready to use.

Who this is for: Site owners publishing articles, guides, or media who want to gate content without modifying an existing build pipeline.

Add the script tag

Include the CDN bundle on every page that needs paywall logic. Pin to a major version so you receive non-breaking updates automatically.

<!-- Pin to a major version in production -->
<script src="https://cdn.jsdelivr.net/npm/@ledewire/browser@0/dist/ledewire.min.js"></script>

Initialise the client

Call Ledewire.init() with your API key (available in the LedeWire merchant dashboard).

const lw = Ledewire.init({
  apiKey: 'your_api_key',

  // Optional: called when the buyer's session expires
  onAuthExpired: () => showLoginModal(),
})

Check checkout state

Call lw.checkout.state(contentId) to determine what the current visitor needs to do next. The next_required_action field drives your UI.

const state = await lw.checkout.state('content-id')

switch (state.checkout_state.next_required_action) {
  case 'authenticate':
    showLoginPrompt()
    break
  case 'fund_wallet':
    showFundWalletPrompt(state.price_cents)
    break
  case 'purchase':
    showPurchaseButton()
    break
}

Authenticate the buyer

Use email/password or Google OAuth. On success, tokens are stored in memory (or localStorage if you prefer persistence).

// Email + password
await lw.auth.loginWithEmail({ email, password })

// Google OAuth (receives the id_token from Google Sign-In)
await lw.auth.loginWithGoogle({ id_token: googleIdToken })

Fund the wallet (Stripe)

LedeWire uses a wallet model. Buyers deposit funds first, then purchase content. Use createPaymentSession to get a Stripe client secret, then present the Stripe Elements UI.

const session = await lw.wallet.createPaymentSession({ amount_cents: 1000 })
// session.public_key  → Stripe publishable key
// session.client_secret → pass to Stripe Elements / PaymentElement

Purchase and reveal content

Purchases are single-use: once the wallet has sufficient funds, buying delivers the content directly in the purchases.create() response — the only place content_body / content_uri ever appear for a purchase. There is no route that serves the same delivery again, so persist it immediately.

// price_cents is the price the buyer was shown; a stale one is refused (422)
const { price_cents } = await lw.checkout.state('content-id')
const purchase = await lw.purchases.create({ content_id: 'content-id', price_cents })
await saveDeliveredContent(purchase) // your own persistence, e.g. IndexedDB

if (purchase.content_body) {
  // content_body is always plain UTF-8 text on the wire — never
  // base64-encoded — so no decoding step is needed. What it contains
  // depends on purchase.content.content_type.
  if (purchase.content.content_type === 'markdown') {
    renderMarkdown(purchase.content_body)
  } else if (purchase.content.content_type === 'html') {
    // Inline HTML content is seller-supplied and must be sanitised
    // before insertion — never assign it to innerHTML directly.
    document.getElementById('article').innerHTML = DOMPurify.sanitize(purchase.content_body)
  }
} else if (purchase.content_uri) {
  // content_uri points at a remote resource (video, PDF, image, or
  // remote HTML). Check the scheme before navigating or linking to it —
  // the URI is seller-supplied and a non-http(s) scheme should not be
  // followed automatically.
  const uri = new URL(purchase.content_uri)
  if (['https:', 'http:'].includes(uri.protocol)) {
    window.location.href = purchase.content_uri
  }
}
Spend cap: every buyer starts with a default daily spend cap. If it's reached, lw.purchases.create() rejects with a SpendCapReachedError (HTTP 402). Funding the wallet does not clear it — the cap resets at err.resetsAt, or the buyer can raise it via lw.user.spendCap.update().
Company members: a buyer whose purchases a Company pays for never sees the Company balance — lw.wallet.balance() returns balance_cents: null, with remaining_cents and company_name instead, and next_required_action is never 'fund_wallet'. Their cap is set by a Company admin (lw.company.members.update()).
Licences: each purchase carries license (type, permissions, document_url, or null when none was recorded), content.publication and content.filing (an SEC filing, else null). lw.purchases.licenseDocument(id) downloads the licence document as { body: ArrayBuffer, contentType } — usually a PDF. Offer it as a download, building the Blob with type: 'application/octet-stream' unless contentType is application/pdf or text/markdown, so a server-sent text/html can never run in your origin; call URL.revokeObjectURL() after the click. Never render it inline.

Full example

See examples/browser-cdn/index.html for a complete working page including wallet funding and the full paywall state machine.

Persistent sessions

By default, tokens are stored in memory and lost on page reload. To keep the buyer logged in across reloads, pass the built-in localStorageAdapter (imported via ES module, not CDN):

import { init, localStorageAdapter } from '@ledewire/browser'

const lw = init({
  apiKey: 'your_api_key',
  storage: localStorageAdapter(),  // persists to localStorage
})