Skip to content

Catalog SDK Quickstart

This guide gets you from zero to a working hosted catalog checkout in a client React app.

Prerequisites

  1. IIMMPACT API credentialsX-Api-Key and HMAC secret for server-to-server requests
  2. A backend server — bootstrap-token creation must happen on your server
  3. A React frontend — the SDK package exposes React components and hooks
  4. An allowed frontend origin — add your production and staging web app origins in the IIMMPACT dashboard

DANGER

Never put API keys, HMAC secrets, or signing code in frontend bundles, mobile apps, or source control.

Step 1: Create a Bootstrap Token (Backend)

When your user is ready to open the catalog, your backend creates a short-lived session token with POST /v2/sdk/catalog/sessions.

bash
API_KEY="your_api_key"
HMAC_SECRET_BASE64="your_hmac_secret_base64"
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')

BODY='{"platform":"web","user_id":"customer-123","ic_number":"900101101234","phone_number":"60123456789","name":"Jane Customer","email":"jane@example.com"}'
BODY_HASH=$(printf "%s" "$BODY" | openssl dgst -sha256 -binary | base64)
CANONICAL="v1:${TIMESTAMP}:${NONCE}:POST::${BODY_HASH}"
SIGNATURE=$(printf "%s" "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$(printf "%s" "$HMAC_SECRET_BASE64" | base64 -d | xxd -p -c 256)" \
  -binary | base64)

curl -X POST https://api.iimmpact.com/v2/sdk/catalog/sessions \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: ${API_KEY}" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "X-Nonce: ${NONCE}" \
  -H "X-Signature: v1=${SIGNATURE}" \
  -d "$BODY"

Response:

json
{
  "session_token": "catalog_sess_7nF1oQ4n6vR2xZ9a",
  "expires_at": "2026-04-30T10:15:00Z"
}

Return only session_token to your frontend. Do not return the API key, HMAC secret, canonical string, or signature.

The session request also creates or updates the Catalog SDK user linked to user_id. The required identity fields are:

FieldRequiredNotes
platformYesweb or react-native
user_idYesYour stable user reference, up to 255 characters
ic_numberYes12-digit Malaysian IC number, hashed before storage
phone_numberYesUser phone number, up to 20 characters
nameNoOptional display name, up to 255 characters
emailNoOptional valid email address, up to 255 characters

INFO

Create a fresh token for each catalog checkout attempt. Treat the token as short-lived and user/session scoped.

Step 2: Install the SDK (Frontend)

bash
npm install @iimmpact-sdn-bhd/catalog-sdk

The package expects React as a peer dependency:

bash
npm install react react-dom

Step 3: Open the Catalog

Fetch the bootstrap token from your backend, then pass it to CatalogLink.

tsx
import { useEffect, useState } from "react";
import { CatalogLink } from "@iimmpact-sdn-bhd/catalog-sdk";

export function CatalogCheckoutButton() {
  const [bootstrapToken, setBootstrapToken] = useState<string | null>(null);

  useEffect(() => {
    async function createCatalogSession() {
      const response = await fetch("/api/catalog-session", { method: "POST" });

      if (!response.ok) {
        throw new Error("Failed to create catalog session");
      }

      const data = await response.json();
      setBootstrapToken(data.session_token);
    }

    createCatalogSession();
  }, []);

  if (!bootstrapToken) {
    return <button disabled>Loading catalog...</button>;
  }

  return (
    <CatalogLink
      bootstrapToken={bootstrapToken}
      widgetUrl="https://catalog.iimmpact.com" // use https://dev-catalog.iimmpact.com for development
      onReady={() => {
        console.log("Catalog is ready");
      }}
      onSuccess={(result) => {
        console.log("Catalog checkout completed", result.orderId);
      }}
      onExit={({ reason }) => {
        console.log("Catalog closed", reason);
      }}
      onError={(error) => {
        console.error("Catalog checkout failed", error.message, error.code);
      }}
    >
      {({ open, status }) => (
        <button disabled={status === "loading"} onClick={open} type="button">
          Open catalog
        </button>
      )}
    </CatalogLink>
  );
}

Step 4: Handle Payment Handoff

The hosted catalog UI calls onSuccess after the user selects a product, enters the required fields, and an IIMMPACT order is ready for payment. The client app should then collect payment through its own payment flow.

typescript
interface CatalogSdkSuccessPayload {
  orderId: string;
  status: "pending_payment";
  totalAmount: string;
  items: Array<{
    product_code: string;
    account_number: string;
    amount: string;
    quantity: number;
    extras?: Record<string, unknown>;
  }>;
}

This is the payload sent to the client app for payment handoff:

FieldPurpose
orderIdIIMMPACT order reference to confirm after client-side payment succeeds
statusInitial order status, normally pending_payment
totalAmountTotal amount the client app should collect from the user
itemsSelected products, account numbers, item amounts, quantities, and extras

Use this metadata to show your payment confirmation screen and start your own payment flow. Do not expect credentials, bearer tokens, or raw payment secrets in the callback.

WARNING

onSuccess is a catalog/order handoff event. It does not mean the client has already collected money. The client app still needs to complete payment through its own rails.

Step 5: Confirm Payment From Your Backend

After your client payment succeeds, your backend should confirm the IIMMPACT order using the orderId from onSuccess plus your payment reference. This is a server-to-server call signed with your API key and HMAC secret. A successful response means IIMMPACT has accepted the payment confirmation and queued fulfillment.

DANGER

Only call this endpoint after your backend has independently verified that payment was successful in your own payment system. Calling it starts asynchronous fulfillment and may trigger the topup or bill payment transaction.

http
POST /v2/sdk/catalog/orders/{orderId}/payment
Content-Type: application/json
X-Api-Key: <client api key>
X-Timestamp: <unix timestamp>
X-Nonce: <unique nonce>
X-Signature: v1=<hmac signature>

{
  "payment_reference": "client-payment-ref-123"
}

Use your own payment reference so both systems can reconcile the transaction later.

Response:

json
{
  "order_id": 12345,
  "status": "processing",
  "payment_reference": "client-payment-ref-123"
}

Order fulfillment is asynchronous. Transaction workers create and check the underlying transactions, then mark the order completed when all transactions reach a terminal successful or failed status. Configure Catalog SDK Webhooks in the IIMMPACT dashboard to receive final order outcomes.

Complete Backend Example (Node.js)

typescript
import { createHash, createHmac, randomUUID } from "node:crypto";

const API_BASE_URL = "https://api.iimmpact.com";

function sha256Base64(value: string): string {
  return createHash("sha256").update(value).digest("base64");
}

export async function createCatalogSession() {
  const apiKey = process.env.IIMMPACT_API_KEY;
  const hmacSecretBase64 = process.env.IIMMPACT_HMAC_SECRET_BASE64;

  if (!apiKey || !hmacSecretBase64) {
    throw new Error("Missing IIMMPACT API credentials");
  }

  const method = "POST";
  const path = "/v2/sdk/catalog/sessions";
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = randomUUID();
  const body = JSON.stringify({
    platform: "web",
    user_id: "customer-123",
    ic_number: "900101101234",
    phone_number: "60123456789",
    name: "Jane Customer",
    email: "jane@example.com",
  });
  const bodyHash = sha256Base64(body);
  const canonical = `v1:${timestamp}:${nonce}:${method}::${bodyHash}`;
  const secret = Buffer.from(hmacSecretBase64, "base64");
  const signature = createHmac("sha256", secret).update(canonical).digest("base64");

  const response = await fetch(`${API_BASE_URL}${path}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": apiKey,
      "X-Timestamp": timestamp,
      "X-Nonce": nonce,
      "X-Signature": `v1=${signature}`,
    },
    body,
  });

  if (!response.ok) {
    throw new Error(`Failed to create catalog session: ${response.status}`);
  }

  return response.json() as Promise<{ session_token: string; expires_at?: string }>;
}

export async function confirmCatalogOrderPayment(orderId: number, paymentReference: string) {
  const apiKey = process.env.IIMMPACT_API_KEY;
  const hmacSecretBase64 = process.env.IIMMPACT_HMAC_SECRET_BASE64;

  if (!apiKey || !hmacSecretBase64) {
    throw new Error("Missing IIMMPACT API credentials");
  }

  const method = "POST";
  const path = `/v2/sdk/catalog/orders/${orderId}/payment`;
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = randomUUID();
  const body = JSON.stringify({ payment_reference: paymentReference });
  const bodyHash = sha256Base64(body);
  const canonical = `v1:${timestamp}:${nonce}:${method}::${bodyHash}`;
  const secret = Buffer.from(hmacSecretBase64, "base64");
  const signature = createHmac("sha256", secret).update(canonical).digest("base64");

  const response = await fetch(`${API_BASE_URL}${path}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": apiKey,
      "X-Timestamp": timestamp,
      "X-Nonce": nonce,
      "X-Signature": `v1=${signature}`,
    },
    body,
  });

  if (!response.ok) {
    throw new Error(`Failed to confirm catalog order payment: ${response.status}`);
  }

  return response.json() as Promise<{ order_id: number; status: string; payment_reference?: string }>;
}

Production Checklist

  • Create bootstrap tokens on your backend only
  • Return only session_token to the browser
  • Use https://catalog.iimmpact.com as widgetUrl in production
  • Use https://dev-catalog.iimmpact.com as widgetUrl during development and integration testing
  • Add your production and staging frontend origins in the IIMMPACT dashboard
  • Set up Catalog SDK Webhooks in the IIMMPACT dashboard for fulfillment updates
  • Handle onSuccess, onExit, and onError
  • Treat onSuccess as the payment handoff point, then complete payment in the client app
  • Call payment confirmation only after your backend verifies successful payment in your own system
  • Treat payment confirmation as asynchronous; use order status, transaction history, or Catalog SDK Webhooks for fulfillment outcome
  • Fetch a new token if a user retries after a token expires

IIMMPACT API Documentation