StrimzStrimz
SDKs

@strimz/sdk

The server SDK for Node.js + Bun.

The server SDK is the main way most teams use Strimz. It's fully typed, validates every response with Zod, and retries the right kinds of failures automatically (5xx errors and rate limits).

Install

pnpm add @strimz/sdk
npm  install @strimz/sdk
yarn add @strimz/sdk
bun  add @strimz/sdk

Construct

import { StrimzClient } from '@strimz/sdk'
 
const strimz = new StrimzClient({
  apiKey: process.env.STRIMZ_KEY!,
  baseUrl: 'https://api.strimz.finance', // optional override; defaults to live
  timeoutMs: 30_000,
  maxRetries: 3,
})

The constructor doesn't make any network calls. It throws StrimzAuthenticationError unless apiKey is a secret key (sk_test_… or sk_live_…). StrimzClient is server-side only: never ship a secret key to the browser. Browser code uses StrimzBrowserClient from @strimz/sdk/browser or the React SDK, both with a publishable key.

Where StrimzClient runs

StrimzClient refuses to start in a browser runtime. There it throws StrimzAuthenticationError with code secret_key_in_browser, no httpStatus, and a message that links to Create sessions on your server. The message never contains your key. There is no option to turn the check off.

The SDK decides the runtime from the globals it can see. The first row that matches wins:

OrderSignalRuntime
1EdgeRuntime is defined (Vercel Edge, Next.js middleware)server
2Deno is definedserver
3Bun is definedserver
4navigator.userAgent is Cloudflare-Workersserver
5process.versions.electron is set and process.type is rendererbrowser
6process.versions.node is set (including Node test runs with jsdom)server
7window and document are definedbrowser
8self is a WorkerGlobalScope (web, shared and service workers)browser
9navigator.product is ReactNativebrowser
10anything elseserver

React Native counts as a browser: an app bundle can be unpacked as easily as a web bundle, and React Native has no CORS check to stop the call. Electron renderers and React Native apps should call your own server, like any web page.

import { StrimzAuthenticationError, StrimzClient } from '@strimz/sdk'
 
try {
  new StrimzClient({ apiKey: key })
} catch (e) {
  if (e instanceof StrimzAuthenticationError && e.code === 'secret_key_in_browser') {
    console.error(e.message)
  }
}

The check is a guard rail: by the time the constructor throws, the key is already in your bundle. Rotate any secret key that has shipped to a browser.

Resources

Every API resource is exposed as a property:

strimz.merchants
strimz.apiKeys
strimz.customers
strimz.paymentSessions
strimz.transactions
strimz.subscriptionPlans
strimz.subscriptions
strimz.refunds
strimz.invoices
strimz.webhookEndpoints
strimz.webhookDeliveries
strimz.storefronts
strimz.agents

Each follows the same create | retrieve | list shape, plus actions such as cancel or revoke. There are no generic update methods; agents.updateConfig, customers.upsert and storefronts.upsert change existing records. Returns are typed:

const session = await strimz.paymentSessions.create({
  amount: '50000000',
  currency: 'USDC',
})
//    ^? PaymentSession  (typed, every field, with .checkoutUrl etc.)

Pagination

List methods return a { data, nextCursor, hasMore } envelope:

let cursor: string | null = null
do {
  const page = await strimz.subscriptions.list({ limit: 50, cursor })
  for (const sub of page.data) {
    /* ... */
  }
  cursor = page.nextCursor
} while (cursor)

Errors

Every error is a StrimzError or a subclass of it. Catch the base class and instanceof-narrow as needed. A 400, such as invalid_request, is a plain StrimzError, so check code:

import {
  StrimzError,
  StrimzAuthenticationError,
  StrimzRateLimitError,
  StrimzNotFoundError,
} from '@strimz/sdk'
 
try {
  await strimz.paymentSessions.create({ ... })
} catch (e) {
  if (e instanceof StrimzError && e.code === 'invalid_request') {
    console.error('field', e.param, 'failed', e.message)
  } else if (e instanceof StrimzRateLimitError) {
    await sleep(e.retryAfterMs)
    /* retry */
  } else if (e instanceof StrimzError) {
    /* fallthrough */
  }
}

See Errors for the full list.

Webhook signature verification

verifyWebhookSignature(payload, signatureHeader, secret, options?) is async. Pass the raw request body as a string, exactly as received, then check result.valid:

import { verifyWebhookSignature } from '@strimz/sdk'
 
const result = await verifyWebhookSignature(
  rawBody,
  signatureHeader,
  process.env.STRIMZ_WEBHOOK_SECRET!,
  { toleranceSeconds: 300 }, // optional; defaults to 300
)
if (!result.valid) {
  console.error('rejected webhook:', result.reason)
}

result.reason is one of malformed_header, missing_timestamp, missing_signature, timestamp_out_of_range, or signature_mismatch.

Constant-time comparison; safe to use directly without wrapping in your own crypto.timingSafeEqual call.

Idempotency

create methods accept an idempotencyKey option. The SDK generates one automatically if you don't provide one:

await strimz.paymentSessions.create(input, { idempotencyKey: 'order-' + orderId })

See Idempotency.

Compatibility

  • Node.js ≥ 18 (uses fetch)
  • Bun ≥ 1.0
  • Deno via the npm shim
  • Cloudflare Workers (@strimz/sdk is Edge-runtime safe. It uses globalThis.fetch and Web Crypto API, no Node-builtins)

Source

github.com/StrimzLab/strimz/tree/main/packages/sdk

On this page