Node.js SDK

@loyalty/node is the official Node.js SDK. It wraps every endpoint with full TypeScript types, automatic error handling, and built-in webhook signature verification — zero dependencies, native fetch.

Install

BASH
npm install @loyalty/node
# or: pnpm add @loyalty/node · yarn add @loyalty/node

Requires Node.js 18+ (for native fetch). Ships with its own type declarations — no @types package needed.

Initialise the client

Create one client and reuse it. Keep your apiKey in an environment variable — never hard-code it or expose it to the browser.

loyalty.ts
import { LoyaltyClient } from '@loyalty/node';

const loyalty = new LoyaltyClient({
  tenantId: process.env.LOYALTY_TENANT_ID!,
  apiKey:   process.env.LOYALTY_API_KEY!,   // sk_live_* or sk_test_*
  // baseUrl is optional — defaults to the production engine
});

Earn — award points & rewards

evaluate() runs the basket against all active campaigns. It awards points and issues any tangible rewards (coupons, gift cards, free items, discount vouchers) in a single call. Each matched campaign returns a rewardIds array.

earn.ts
// Award points and/or issue rewards for a basket — auto-creates the member
const result = await loyalty.evaluate({
  transactionRef: 'receipt_001',
  memberRef:      'customer_27821234567',
  amount:         15000,   // always in cents
});

console.log(result.pointsAwarded);          // 150
console.log(result.pointsBalance);           // 150
console.log(result.campaigns[0].rewardIds);  // ['rwd_...'] when a reward fired

Burn — quote & redeem

Preview a discount with redeemQuote() (no writes), then commit it with redeem(). Both evaluate() and redeem() are idempotent on transactionRef.

burn.ts
// Preview a redemption — no database writes
const quote = await loyalty.redeemQuote({
  memberRef:       'customer_27821234567',
  amount:          15000,
  pointsRequested: 100,
});
console.log(quote.discountAmount);  // 100 (cents)

// Commit the redemption — idempotent on transactionRef
const redeemed = await loyalty.redeem({
  transactionRef:  'redeem_001',
  memberRef:       'customer_27821234567',
  amount:          15000,
  pointsRequested: 100,
});
console.log(redeemed.balanceAfter);

Verify webhooks

Every delivery is signed with HMAC-SHA256 in the X-Loyalty-Signature header. The SDK ships a constant-time verifyWebhookSignature() helper — always verify against the raw request body before processing.

webhook.ts
import { verifyWebhookSignature } from '@loyalty/node';
import express from 'express';

const app = express();

app.post('/webhooks/loyalty', express.text({ type: '*/*' }), (req, res) => {
  const valid = verifyWebhookSignature(
    req.body,                              // raw string body
    req.headers['x-loyalty-signature'] as string,
    process.env.LOYALTY_WEBHOOK_SECRET!,
  );
  if (!valid) return res.status(401).end();

  const event = JSON.parse(req.body);
  switch (event.event) {
    case 'transaction.processed': /* … */ break;
    case 'reward.issued':         /* … */ break;
  }
  res.status(200).json({ received: true });
});

Error handling

Failed requests throw a typed LoyaltyError carrying the engine's code, HTTP status, and a human-readable detail. See the error reference for the full code list.

errors.ts
import { LoyaltyError } from '@loyalty/node';

try {
  await loyalty.redeem({ /* … */ });
} catch (err) {
  if (err instanceof LoyaltyError) {
    console.error(err.code);    // e.g. 'INSUFFICIENT_POINTS'
    console.error(err.status);  // e.g. 422
    console.error(err.detail);  // human-readable explanation
  } else {
    throw err; // network or unexpected error
  }
}
Tip: Start against sk_test_* keys — they hit an isolated environment where no real transactions occur. See the testing guide.
Node.js SDK — Loyalty Engine Docs