npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@laguna-team/whitelabel-sdk

v0.1.1

Published

Official TypeScript SDK for the Laguna Whitelabel API — embed cashback in your app, mint affiliate links, and verify webhooks.

Readme

Laguna Whitelabel TypeScript SDK

npm version License: MIT

The Laguna Whitelabel SDK provides convenient access to the Laguna Whitelabel API from applications written in server-side TypeScript or JavaScript. Embed crypto cashback into your app: discover affiliate merchants, mint tracked links, verify webhooks, and disburse USDC settlements.

Documentation

See the API reference for complete endpoint documentation. This README covers SDK usage; the API docs cover the underlying request/response shapes, error codes, and rate limits.

Requirements

  • Node.js 18+ (uses native fetch and crypto.subtle)
  • A Laguna API key (sandbox lg_test_* or live lg_live_*)

Getting an API key

Self-serve sign-up is coming soon. For now, onboarding is assisted — email [email protected] with:

  • Your company name and a short description of how you plan to use Laguna
  • The merchants / categories you're most interested in
  • The country / region your users are in

We'll set up your partner record, issue a sandbox key (lg_test_*) for integration, and walk you through the live key (lg_live_*) once you're ready.

Installation

npm install @laguna-team/whitelabel-sdk
pnpm add @laguna-team/whitelabel-sdk
yarn add @laguna-team/whitelabel-sdk

Usage

The library needs to be configured with your account's API key. Pass it to LagunaClient on instantiation:

import { LagunaClient } from '@laguna-team/whitelabel-sdk'

const laguna = new LagunaClient({
  apiKey: process.env.LAGUNA_API_KEY!,
})

const link = await laguna.links.create({
  merchant_id: 'shopee',
  partner_user_id: 'user_abc123', // your internal user ID — opaque to Laguna
  geo: 'SG',
})

console.log(link.shortlink) // open this in the user's browser

TypeScript

The library is written in TypeScript and ships with type definitions. All request and response payloads are strictly typed:

import type {
  CatalogResponse,
  MerchantDetail,
  CreateLinkParams,
  CreateLinkResult,
  WebhookPayload,
} from '@laguna-team/whitelabel-sdk'

Resources

The SDK is organized by resource. Each resource exposes a small set of methods that mirror the REST endpoints.

catalog — discover merchants

const { merchants } = await laguna.catalog.list({ geo: 'SG' })
// each merchant has `subscription_status`: 'pending' | 'approved' | 'rejected' | 'revoked' | null

subscriptions — request access to merchants

Partners must be approved-subscribed to a merchant before minting links for it.

await laguna.subscriptions.request(['shopee', 'lazada'])
const subs = await laguna.subscriptions.list({ status: 'approved' })
await laguna.subscriptions.unsubscribe('shopee') // self-serve

merchants — refresh live rates

const { merchants, cache_ttl } = await laguna.merchants.list({ geo: 'SG' })
// store rates in your DB; refresh every `cache_ttl` seconds

links — mint a tracked link

const link = await laguna.links.create({
  merchant_id: 'shopee',
  partner_user_id: 'user_abc123',
  geo: 'SG',
})

disbursements — pay out to a user wallet (Model 1)

const result = await laguna.disbursements.create({
  transaction_id: event.transaction_id, // idempotent on this
  user_wallet_address: '0xUSER_WALLET',
})

const status = await laguna.disbursements.get(result.disbursement_id)
console.log(status.tx_hash) // populated when status === 'completed'

earnings — check accrued balance

const earnings = await laguna.earnings.get()
console.log(`${earnings.available} ${earnings.settlement_token} ready to withdraw`)

withdrawals — settle to your registered wallet

const w = await laguna.withdrawals.create({ amount: 100 })
const status = await laguna.withdrawals.get(w.withdrawal_id)

The destination wallet address is taken from your partner record on file — never accepted in the request body. This prevents funds redirection if your API key is compromised.

Webhooks

Laguna sends an HTTP POST to your registered webhook URL whenever a conversion is confirmed or reversed. Verify the X-Laguna-Signature header before processing the body:

import express from 'express'
import { parseWebhook, LagunaWebhookSignatureError } from '@laguna-team/whitelabel-sdk'

app.post(
  '/webhooks/laguna',
  express.raw({ type: 'application/json' }), // raw body required
  (req, res) => {
    try {
      const event = parseWebhook(
        req.body.toString('utf8'),
        req.headers['x-laguna-signature'] as string,
        process.env.LAGUNA_WEBHOOK_SECRET!,
      )

      // event is typed as WebhookPayload
      console.log(`${event.partner_user_id} earned ${event.user_amount} ${event.settlement_token}`)
      res.sendStatus(200)
    } catch (err) {
      if (err instanceof LagunaWebhookSignatureError) {
        return res.status(401).json({ error: 'Invalid signature' })
      }
      throw err
    }
  },
)

Important: verify against the raw request body. JSON.parse(...) then JSON.stringify(...) produces different bytes (key order, whitespace) and the signature will fail.

If you need a boolean check without parsing, use verifyWebhookSignature(rawBody, signatureHeader, secret) — same primitive, returns true | false instead of the parsed payload.

Handling errors

When the SDK can't process a request, it throws a typed error:

import {
  LagunaError,
  LagunaAuthError,        // 401 — invalid or revoked API key
  LagunaScopeError,       // 403 — merchant not subscribed
  LagunaValidationError,  // 400 / 422 — invalid request
  LagunaRateLimitError,   // 429 — exposes `retryAfterSeconds`
  LagunaServerError,      // 5xx — automatically retried up to `maxRetries`
  LagunaNetworkError,     // network failure or timeout
} from '@laguna-team/whitelabel-sdk'

try {
  await laguna.links.create({ merchant_id: 'unknown', partner_user_id: 'x' })
} catch (err) {
  if (err instanceof LagunaScopeError) {
    // subscribe to the merchant first
  } else if (err instanceof LagunaRateLimitError) {
    console.log(`Retry in ${err.retryAfterSeconds}s`)
  } else if (err instanceof LagunaError) {
    console.log(`${err.code}: ${err.message}`)
  }
}

Configuration

const laguna = new LagunaClient({
  apiKey: 'lg_live_...',     // required — prefix picks the env (see below)
  webhookSecret: '...',      // optional — used by webhook helpers
  timeoutMs: 30_000,         // per-request timeout in ms
  maxRetries: 2,             // for transient 5xx + network errors
  fetch: customFetch,        // override (testing, polyfills)
})

Sandbox vs production

The SDK targets the right environment automatically based on your API key prefix:

| Prefix | Environment | Base URL | |---|---|---| | lg_live_* | Production | https://api.laguna.network | | lg_test_* | Staging | https://api-stg.laguna.network |

You don't need to specify a base URL — just drop in your key. Switching from sandbox to production is a one-character change in your env config (lg_test_...lg_live_...).

Sandbox returns isolated test data and dry-runs disbursements. Production hits real partner accounts and settles on-chain. Both share the same SDK surface and webhook payload shape.

If Laguna support gives you a non-standard endpoint (regional, joint development, etc.), you can override with the baseUrl config option.

Retries

Transient failures (HTTP 5xx and network errors) are retried automatically with exponential backoff. POST endpoints accept an optional Idempotency-Key header so retries are safe:

await laguna.links.create(
  { merchant_id: 'shopee', partner_user_id: 'user_abc123' },
  { idempotencyKey: 'order_xyz_link_attempt_1' },
)

Timeouts

Set timeoutMs on the client (default 30s):

const laguna = new LagunaClient({
  apiKey: process.env.LAGUNA_API_KEY!,
  timeoutMs: 5_000,
})

Versioning

This package follows semver. Breaking changes are released only in major versions and noted in the CHANGELOG.

The SDK pins the API version it targets internally — upgrading the SDK is the supported way to access new endpoints. The base URL stays the same across versions.

Contributing

Issues and pull requests are welcome on GitHub. For commercial questions or to obtain API keys, contact [email protected].

License

MIT © Laguna