Skip to content

TypeScript (Web & Server)

@maxint/orca-sdk ships two separate clients in one package:

  • OrcaClient — runs in the browser, drives your paywall and checkout redirect
  • OrcaServerClient — runs on your backend, checks access, manages customers, verifies webhooks

You’ll almost always use both together: OrcaClient on the frontend to sell, OrcaServerClient on the backend to trust what was sold.

Terminal window
pnpm add @maxint/orca-sdk

Since there’s no App Store or Play Store on the web, checkout goes through Stripe or GoCardless — OrcaClient handles building the checkout session and redirecting the browser.

import { OrcaClient } from '@maxint/orca-sdk'
const orca = new OrcaClient('public_api_key', 'sandbox') // 'prod' for live
await orca.identify('user@example.com')
const products = await orca.queryProducts('stripe') // or 'gocardless'
products.forEach((p) => {
console.log(`${p.name} — ${p.formattedPrice}`)
})

Unlike the mobile SDKs, you have to say which store to price against, since a web app might route customers through either Stripe or GoCardless.

const entitlements = await orca.listEntitlements()
const chosen = entitlements.find((e) => e.id === 'pro_monthly')!
await orca.purchase({
store: 'stripe',
entitlement: chosen,
redirectUrl: 'https://yourapp.com/checkout/success',
failureRedirectUrl: 'https://yourapp.com/checkout/failure',
})

By default this redirects the browser to Stripe/GoCardless checkout for you. If you’re managing navigation yourself (e.g. opening it in a new tab), pass autoRedirect: false and use the returned URL:

const checkoutUrl = await orca.purchase({
store: 'stripe',
entitlement: chosen,
redirectUrl: 'https://yourapp.com/checkout/success',
failureRedirectUrl: 'https://yourapp.com/checkout/failure',
autoRedirect: false,
})
window.open(checkoutUrl, '_blank')

If the customer is switching from one paid plan to another, tell Orca what they’re switching from so the price gets prorated:

await orca.purchase({
store: 'stripe',
entitlement: newPlan,
redirectUrl: 'https://yourapp.com/checkout/success',
failureRedirectUrl: 'https://yourapp.com/checkout/failure',
proratedProductId: currentPlan.products.stripe.product_id,
prorationMode: 'upgrade', // or 'downgrade'
})

Once the customer comes back from checkout (or on any page load), check what’s active so you can unlock the right UI:

const active = await orca.getActiveEntitlements()
const isPro = active.some((e) => e.entitlement_id === 'pro_tier')
orca.logout()

Never trust the browser alone for access control — always confirm on the backend before serving anything paid. That’s what OrcaServerClient is for.

import { OrcaServerClient } from '@maxint/orca-sdk'
const orca = new OrcaServerClient(process.env.ORCA_API_KEY!)

This uses your private API key, never the public one — keep it server-side only.

The most common thing you’ll do: check if a logged-in user actually has access before returning paid content.

app.get('/premium-content', async (req, res) => {
const active = await orca.getActiveEntitlements(req.user.email, 'prod')
const hasAccess = active.some((e) => e.entitlement_id === 'pro_tier')
if (!hasAccess) {
return res.status(402).send('Subscribe to access this.')
}
res.json({ content: '...' })
})

Useful for admin dashboards or support tooling:

const customer = await orca.getCustomerInfo('user@example.com', 'prod')
const { customers, cursor } = await orca.listCustomers(20)
await orca.cancelStripeSubscription('prod', 'entitlement_id', 'user@example.com')
// or
await orca.cancelGocardlessSubscription('prod', 'entitlement_id', 'user@example.com')

Orca sends you a webhook every time an entitlement changes (new purchase, renewal, cancellation, refund). Verify the signature before trusting the payload:

app.post('/webhooks/orca', async (req, res) => {
const event = await orca.constructWebhookEvent({
webhookPublicKey: process.env.ORCA_WEBHOOK_PUBLIC_KEY!,
rawPayload: req.rawBody,
signatureHeader: req.headers['x-orca-signature'] as string,
timestampHeader: req.headers['x-orca-timestamp'] as string,
})
// event is the customer's updated entitlement — update your DB here
res.sendStatus(200)
})

This throws if the signature doesn’t match or the timestamp is more than 5 minutes old, so a plain try/catch around it is enough to reject bad requests.

The typical setup looks like this:

  1. Frontend calls orca.purchase(...) — customer completes checkout on Stripe/GoCardless.
  2. Orca sends a webhook to your backend — you verify it with constructWebhookEvent and update your own database.
  3. Frontend calls orca.getActiveEntitlements() (or your backend calls getActiveEntitlements on the server client) to decide what to unlock.

You don’t strictly need the webhook if your backend always calls getActiveEntitlements live before serving paid content — but webhooks let you react to renewals and cancellations without polling.

If you’re shipping a Tauri app, tauri-plugin-orca-api gives you the same OrcaClient-shaped API but automatically routes purchases through orca-apple or orca-android on mobile, and through @maxint/orca-sdk on web/Windows/Linux. See the package README for setup.