TypeScript (Web & Server)
@maxint/orca-sdk ships two separate clients in one package:
OrcaClient— runs in the browser, drives your paywall and checkout redirectOrcaServerClient— 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.
Install it
Section titled “Install it”pnpm add @maxint/orca-sdkClient-side: selling on the web
Section titled “Client-side: selling on the web”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.
1. Create the client
Section titled “1. Create the client”import { OrcaClient } from '@maxint/orca-sdk'
const orca = new OrcaClient('public_api_key', 'sandbox') // 'prod' for live2. Identify the customer
Section titled “2. Identify the customer”await orca.identify('user@example.com')3. Show the paywall
Section titled “3. Show the paywall”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.
4. Send them to checkout
Section titled “4. Send them to checkout”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')5. Upgrading or downgrading a plan
Section titled “5. Upgrading or downgrading a plan”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'})6. Checking what they already have
Section titled “6. Checking what they already have”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')7. Sign out
Section titled “7. Sign out”orca.logout()Server-side: the source of truth
Section titled “Server-side: the source of truth”Never trust the browser alone for access control — always confirm on the backend before serving anything paid. That’s what OrcaServerClient is for.
1. Create the client with your secret key
Section titled “1. Create the client with your secret key”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.
2. Gate a request
Section titled “2. Gate a request”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: '...' })})3. Look up a customer
Section titled “3. Look up a customer”Useful for admin dashboards or support tooling:
const customer = await orca.getCustomerInfo('user@example.com', 'prod')const { customers, cursor } = await orca.listCustomers(20)4. Cancel a subscription on their behalf
Section titled “4. Cancel a subscription on their behalf”await orca.cancelStripeSubscription('prod', 'entitlement_id', 'user@example.com')// orawait orca.cancelGocardlessSubscription('prod', 'entitlement_id', 'user@example.com')5. Verify webhooks
Section titled “5. Verify webhooks”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.
Putting client and server together
Section titled “Putting client and server together”The typical setup looks like this:
- Frontend calls
orca.purchase(...)— customer completes checkout on Stripe/GoCardless. - Orca sends a webhook to your backend — you verify it with
constructWebhookEventand update your own database. - Frontend calls
orca.getActiveEntitlements()(or your backend callsgetActiveEntitlementson 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.
Also available: Tauri desktop apps
Section titled “Also available: Tauri desktop apps”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.