The SDK wraps every API endpoint and gives you full TypeScript types — request bodies, response shapes, and event payloads. It’s built on fetch, has no heavyweight dependencies, and works in Node.js 18+.
Install
Initialise
import { Plinth } from '@nomba/sdk';
const client = new Plinth({
apiKey: process.env.NOMBA_API_KEY!,
});
Pass apiKey explicitly or set NOMBA_API_KEY as an environment variable — the SDK picks it up automatically if you don’t pass it.
Resources
Every API resource maps to a method on the client:
client.customers
client.customers.create(body) // POST /v1/customers
client.customers.get(id) // GET /v1/customers/:id
client.customers.list(params?) // GET /v1/customers
client.customers.entitlements(id) // GET /v1/customers/:id/entitlements
client.customers.provisionVirtualAccount(id) // POST /v1/customers/:id/virtual-account
client.customers.getVirtualAccount(id) // GET /v1/customers/:id/virtual-account
client.subscriptions
client.subscriptions.create(body, opts?) // POST /v1/subscriptions
client.subscriptions.get(id) // GET /v1/subscriptions/:id
client.subscriptions.status(id) // GET /v1/subscriptions/:id/status
client.subscriptions.cancel(id) // POST /v1/subscriptions/:id/cancel (policy-driven)
client.subscriptions.reactivate(id) // POST /v1/subscriptions/:id/reactivate
client.subscriptions.previewChange(id, body) // POST /v1/subscriptions/:id/preview-change
client.subscriptions.change(id, body) // POST /v1/subscriptions/:id/change
client.subscriptions.changeCheckout(id, body) // POST /v1/subscriptions/:id/change-checkout
client.subscriptions.checkoutLink(id, body?) // POST /v1/subscriptions/:id/checkout-link
client.subscriptions.cancelScheduledChange(id, changeId) // DELETE /v1/subscriptions/:id/scheduled-change/:changeId
client.plans
client.planGroups.create(body) // POST /v1/plan-groups
client.planGroups.createPlan(groupId, body) // POST /v1/plan-groups/:id/plans
client.plans.list(params?) // GET /v1/plans
client.invoices
client.invoices.list(params?) // GET /v1/invoices
client.invoices.get(id) // GET /v1/invoices/:id
client.invoices.lineItems(id) // GET /v1/invoices/:id/line-items
Webhook handler
constructEvent verifies the Plinth-Signature header against your endpoint secret (whsec_…) and
returns the parsed event. Pass the raw body. For a framework-agnostic version and the exact
signing scheme, see Receiving webhooks.
import { constructEvent } from '@nomba/sdk/webhooks';
import express from 'express';
const app = express();
app.post('/api/plinth/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
let event;
try {
event = constructEvent(
req.body, // raw body
req.headers['plinth-signature'] as string, // t=<unix>,v1=<hmac>
process.env.PLINTH_WEBHOOK_SECRET!, // whsec_…
);
} catch {
return res.status(401).send('invalid signature');
}
switch (event.type) {
case 'subscription.activated':
await grantFullAccess(event.data.object.customerId);
break;
case 'subscription.past_due':
await showPaymentWarning(event.data.object.customerId);
break;
case 'subscription.delinquent':
await revokeAccess(event.data.object.customerId);
break;
case 'subscription.recovered':
await restoreAccess(event.data.object.customerId);
break;
}
res.sendStatus(200);
});
constructEvent throws if the signature doesn’t match — return 401 in that case.
Error handling
The SDK throws typed errors for every failure case:
import { NombaApiError, NombaSignatureError } from '@nomba/sdk';
try {
const customer = await client.customers.get('cus_invalid...');
} catch (err) {
if (err instanceof NombaApiError) {
console.error(err.status); // e.g., 404
console.error(err.message); // "Customer not found"
console.error(err.code); // "not_found"
}
}
NombaApiError covers all HTTP errors (4xx, 5xx). NombaSignatureError covers invalid webhook signatures.
Idempotency keys
Pass an idempotency key in options to safely retry:
const subscription = await client.subscriptions.create(body, {
idempotencyKey: `sub-${customerId}-${planId}-${Date.now()}`,
});
A Python SDK is in progress. In the meantime, the REST API works with any HTTP client — see the curl examples throughout the docs.