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

@selorax/app-sdk

v2.1.0

Published

SDK for building apps on the SeloraX platform — session auth, platform API client, webhook verification

Downloads

215

Readme

selorax-app-sdk

Build apps for the SeloraX e-commerce platform

npm version npm downloads license node

Getting Started  •  Guides  •  Extensions  •  API Reference  •  Examples  •  Troubleshooting


Installation

npm install selorax-app-sdk

[!NOTE] Peer dependency: Your project must have express v4 or v5 installed.

| Requirement | Version | Notes | |:------------|:--------|:------| | Node.js | ≥ 18 | Uses native fetch | | Express | v4 or v5 | Peer dependency | | SeloraX Developer Account | — | Register here to get credentials. See docs for guides. |


Getting Started

1. Get your credentials

Register your app on the SeloraX Developer Portal. You'll receive:

| Credential | Prefix | Example | |:-----------|:------:|:--------| | Client ID | sx_app_ | sx_app_1b16e193a28d2640... | | Client Secret | sx_secret_ | sx_secret_dd0f155b6e333f59... | | Session Signing Key | — | ac4d5804b66820c347f626... | | Webhook Secret | whsec_ | whsec_8a3f1d2e5b7c9a0f... |

2. Configure environment

Create a .env file:

# ━━━ Required ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SELORAX_API_URL=https://api.selorax.io/api
SELORAX_CLIENT_ID=sx_app_your_client_id
SELORAX_CLIENT_SECRET=sx_secret_your_client_secret

# ━━━ Optional ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SESSION_SIGNING_KEY=your_64_char_hex_signing_key
WEBHOOK_SIGNING_SECRET=whsec_your_webhook_secret

3. Write your first app

require('dotenv').config();
const express = require('express');
const { seloraxAuth, seloraxApi, createWebhookRouter } = require('selorax-app-sdk');

const app = express();

app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf.toString(); },
}));

// Webhook events from the platform
app.use('/webhooks', createWebhookRouter({
  'order.status_changed': (event, req, res) => {
    console.log(`Order #${event.data.order_id} → ${event.data.status}`);
    res.json({ message: 'OK', status: 200 });
  },
}));

// Your app's dashboard API (protected)
app.get('/api/dashboard', seloraxAuth, async (req, res) => {
  const store = await seloraxApi.store.get(req.session.store_id);
  res.json({ data: store.data });
});

app.listen(5010);

Guides

Authentication

The problem: When a merchant opens your app in the SeloraX dashboard, the app loads inside an iframe. You need to securely identify which merchant is using your app — without asking them to log in again.

The solution: The dashboard sends a short-lived session token to your iframe via postMessage. Your frontend passes it to your backend in every request. The SDK verifies it automatically.

How the token flows

┌─────────────────────┐         ┌─────────────────────┐         ┌─────────────────────┐
│  SeloraX Dashboard  │         │  Your Frontend      │         │  Your Backend        │
│                     │         │  (iframe)           │         │  (Express)           │
└─────────┬───────────┘         └─────────┬───────────┘         └─────────┬───────────┘
          │                               │                               │
          │  1. Load iframe               │                               │
          │  ?store_id=22&host=...        │                               │
          │──────────────────────────────>│                               │
          │                               │                               │
          │  2. Session token             │                               │
          │  via postMessage              │                               │
          │──────────────────────────────>│                               │
          │                               │                               │
          │                               │  3. GET /api/dashboard        │
          │                               │  X-Session-Token: eyJ...      │
          │                               │──────────────────────────────>│
          │                               │                               │
          │                               │                  4. seloraxAuth
          │                               │                     verifies  │
          │                               │                     ↓         │
          │                               │                  req.session = │
          │                               │                  { store_id,  │
          │                               │                    install_id,│
          │                               │                    app_id }   │
          │                               │                               │
          │                               │  5. JSON response             │
          │                               │<──────────────────────────────│

Usage

const { seloraxAuth } = require('selorax-app-sdk');

app.get('/api/my-feature', seloraxAuth, (req, res) => {
  // req.session is populated after verification:
  const { store_id, installation_id, app_id } = req.session;

  res.json({ store_id });
});

Verification modes

The middleware picks the best available method automatically:

| Mode | Condition | Speed | How it works | |:-----|:----------|:-----:|:-------------| | Local | SESSION_SIGNING_KEY is set | < 1ms | Verifies JWT signature locally with HMAC-SHA256. No network call. | | Platform | Signing key not set | ~50ms | Calls /apps/session/verify with your client credentials. Results cached for 5 minutes. |

[!TIP] Set SESSION_SIGNING_KEY in production for the fastest possible auth. Without it, the SDK still works — it just makes an HTTP call to the platform (cached).


Platform API

The problem: Your app needs to read and write merchant data — orders, products, customers, billing. You'd have to handle authentication headers, URL building, error parsing, and query parameters for every request.

The solution: seloraxApi provides a namespaced client that handles all of this. Just call the method with a storeId and get back parsed JSON.

const { seloraxApi } = require('selorax-app-sdk');

[!IMPORTANT] Every API call authenticates using your SELORAX_CLIENT_ID and SELORAX_CLIENT_SECRET. These are sent as headers on every request and never expire — similar to Shopify's offline access tokens.

// List with pagination and filters
const orders = await seloraxApi.orders.list(storeId, {
  page: 1,
  limit: 20,
});

// Get a single order by ID
const order = await seloraxApi.orders.get(storeId, orderId);

Response structure:

{
  "message": "Orders fetched.",
  "data": [
    {
      "id": 1234,
      "order_number": "SX-1234",
      "status": "processing",
      "total": 1500,
      "customer_name": "John Doe",
      "customer_phone": "01700000000"
    }
  ],
  "status": 200
}
const products = await seloraxApi.products.list(storeId, { page: 1 });
const product  = await seloraxApi.products.get(storeId, productId);
const customers = await seloraxApi.customers.list(storeId, { page: 1 });
const customer  = await seloraxApi.customers.get(storeId, userId);
// Returns name, currency, domain, settings, etc.
const store = await seloraxApi.store.get(storeId);
const inventory = await seloraxApi.inventory.list(storeId, { page: 1 });

All merchant payments flow through the SeloraX platform:

// ── One-time or recurring charges ──────────────────

// Create a charge (merchant sees an approval page)
const charge = await seloraxApi.billing.createCharge(storeId, {
  name: 'Pro Plan',
  amount: 500,
  type: 'one_time',   // or 'recurring'
});
// → charge.data.confirmation_url  (redirect merchant here)

// Check if merchant approved
const status = await seloraxApi.billing.getCharge(storeId, chargeId);


// ── Wallet (for usage-based billing) ───────────────

// Check balance
const wallet = await seloraxApi.billing.getWallet(storeId);

// Debit per-usage (e.g., SMS sent, API call made)
await seloraxApi.billing.debitWallet(storeId, 10, 'SMS delivery', {
  sms_id: 456,
  phone: '01700000000',
});

// Request a top-up (merchant pays via payment gateway)
await seloraxApi.billing.createTopup(storeId, 500);
// List current subscriptions
const subs = await seloraxApi.webhooks.list(storeId);

// Subscribe to an event
await seloraxApi.webhooks.create(storeId, {
  topic: 'order.status_changed',
  url: 'https://myapp.com/webhooks/receive',
});
// GET
const data = await seloraxApi.apiCall(storeId, 'GET', '/apps/v1/custom-endpoint');

// POST with body
const result = await seloraxApi.apiCall(storeId, 'POST', '/apps/v1/custom-endpoint', {
  key: 'value',
});

Error handling

All methods throw on non-2xx responses with a structured error:

try {
  await seloraxApi.orders.get(storeId, 'bad-id');
} catch (err) {
  err.message   // → "Order not found"
  err.status    // → 404
  err.data      // → { message: "Order not found", status: 404 }
}

Webhooks

The problem: When events happen on the platform — an order ships, your app gets installed or uninstalled — you need to react in real time. The platform sends signed HTTP requests to your app, but you need to verify they're genuine.

The solution: The SDK provides two approaches: a router factory that handles everything, or a low-level verify function for custom setups.

[!WARNING] Your Express app must capture the raw request body for HMAC verification. Add this before all routes:

app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf.toString(); },
}));

Option A: Router Factory (recommended)

One function — creates a complete webhook endpoint with signature verification:

const { createWebhookRouter } = require('selorax-app-sdk');

const webhooks = createWebhookRouter({

  'order.status_changed': (event, req, res) => {
    const { store_id, data } = event;
    console.log(`[Store ${store_id}] Order #${data.order_id} → "${data.status}"`);

    // Your logic here:
    // - Send push notification
    // - Update your database
    // - Trigger an automation

    res.json({ message: 'Processed', status: 200 });
  },

  'app.installed': (event, req, res) => {
    console.log(`Installed on store ${event.store_id}`);
    // Initialize default settings for this store...
    res.json({ message: 'OK', status: 200 });
  },

  'app.uninstalled': (event, req, res) => {
    console.log(`Uninstalled from store ${event.store_id}`);
    // Clean up data for this store...
    res.json({ message: 'OK', status: 200 });
  },

});

// Mounts at: POST /webhooks/receive
app.use('/webhooks', webhooks);

The event object:

{
  topic: 'order.status_changed',    // event name
  store_id: 22,                      // originating store
  data: {                            // event-specific payload
    order_id: 1234,
    order_number: 'SX-1234',
    status: 'shipped',
    customer_name: 'John Doe',
    customer_phone: '01700000000',
    total: 1500,
    store_name: 'My Store',
  }
}

Option B: Manual Verification

Full control for custom setups:

const { verifyWebhook } = require('selorax-app-sdk');

app.post('/my-endpoint', (req, res) => {
  const isValid = verifyWebhook(
    req.rawBody,                          // raw body string
    req.header('X-SeloraX-Signature'),    // "sha256=<hex>"
    req.header('X-SeloraX-Timestamp'),    // Unix seconds
    process.env.WEBHOOK_SIGNING_SECRET    // "whsec_..."
  );

  if (!isValid) return res.status(401).json({ message: 'Bad signature' });

  // Process the verified event...
  res.json({ message: 'OK' });
});

Security guarantees

| Protection | How it works | |:-----------|:-------------| | Authenticity | HMAC-SHA256 signature over timestamp.body proves the request came from SeloraX | | Timing attack prevention | Signatures compared with crypto.timingSafeEqual | | Replay attack prevention | Requests older than 5 minutes are automatically rejected |


Token Store

The problem: After a merchant installs your app via OAuth, you receive access and refresh tokens. You need to store them somewhere and retrieve them later when making API calls on behalf of that store.

The solution: A simple key-value store keyed by store_id. Ships with an in-memory implementation for development. Swap the backend for production.

const { tokenStore } = require('selorax-app-sdk');

| Method | Returns | Description | |:-------|:--------|:------------| | get(storeId) | object \| null | Retrieve stored tokens | | set(storeId, data) | void | Store tokens | | remove(storeId) | void | Delete tokens | | getAll() | array | List all stored tokens |

// After OAuth code exchange — store the tokens
tokenStore.set(storeId, {
  access_token: 'sx_at_...',
  refresh_token: 'sx_rt_...',
  installation_id: 5,
  scopes: ['read_orders', 'read_products'],
  expires_at: '2025-06-01T00:00:00Z',
});

// Later — retrieve tokens for API calls
const tokens = tokenStore.get(storeId);
// → { store_id, access_token, refresh_token, installation_id, scopes, expires_at, updated_at }

// On uninstall — clean up
tokenStore.remove(storeId);

[!CAUTION] The built-in store is in-memory only — all tokens are lost when the server restarts. For production, replace with MySQL, Redis, or any persistent storage.


Extensions

The problem: You want your app's UI to appear natively inside the SeloraX dashboard — on the main dashboard, order detail pages, product pages, etc. — without building an iframe.

The solution: Define your UI as a JSON component tree in extensions.json, register it via the dev portal API, and handle backend calls when the dashboard proxies requests to your server.

Extension JSON UI

Extensions use a declarative JSON format. Each node has a type, props, and optional children:

{
  "type": "Card",
  "props": { "title": "My Widget" },
  "children": [
    { "type": "Text", "props": { "content": "Hello {{state.name}}" } },
    {
      "type": "Button",
      "props": { "label": "Click Me" },
      "actions": {
        "onClick": {
          "type": "call_backend",
          "url": "/api/extensions/my-action",
          "method": "POST"
        }
      }
    }
  ]
}

Available constants

const { EXTENSION_TARGETS, EXTENSION_COMPONENTS, EXTENSION_ACTIONS } = require('selorax-app-sdk');

// 26 targets — where extensions can appear
EXTENSION_TARGETS.DASHBOARD_WIDGET     // 'dashboard.widget'
EXTENSION_TARGETS.ORDER_DETAIL_BLOCK   // 'order.detail.block'
EXTENSION_TARGETS.NAVIGATION_LINK      // 'navigation.link'

// 50+ component types — what you can render
EXTENSION_COMPONENTS.Card       // 'Card'
EXTENSION_COMPONENTS.Text       // 'Text'
EXTENSION_COMPONENTS.Button     // 'Button'
EXTENSION_COMPONENTS.TextArea   // 'TextArea'
EXTENSION_COMPONENTS.Badge      // 'Badge'

// 8 action types — what can happen on interaction
EXTENSION_ACTIONS.CALL_BACKEND  // 'call_backend'
EXTENSION_ACTIONS.SET_STATE     // 'set_state'
EXTENSION_ACTIONS.NAVIGATE      // 'navigate'

Template syntax

String props support {{expression}} templates:

| Expression | Example | Description | |:-----------|:--------|:------------| | State access | {{state.count}} | Read from extension's local state | | Context access | {{context.order.order_id}} | Read from page context (e.g., current order) | | Negation | {{!state.loaded}} | Boolean negation | | Input value | {{$value}} | Current form input value (in onChange actions) | | Interpolation | "Total: {{state.amount}}" | Inline string interpolation |

Conditional rendering

Any component can have a when prop. It renders only when the expression is truthy:

{ "type": "Text", "props": { "when": "{{state.loaded}}", "content": "Ready!" } }
{ "type": "Spinner", "props": { "when": "{{!state.loaded}}" } }

Response directives

When the dashboard proxies a call_backend action to your server, your response can include directives:

res.json({
    update_state: { count: 42, loaded: true },          // Patch local state
    show_toast: { message: 'Saved!', type: 'success' }, // Toast notification
    navigate: '/3/orders',                                // Redirect user
    open_modal: { title: 'Details', ui: { ... } },      // Open a modal
    close_modal: true,                                    // Close current modal
    refetch: ['ORDERS_KEY'],                             // Invalidate React Query caches
});

Registration

Use scripts/register-extensions.js in the boilerplate, or call the dev portal API directly:

# Register
curl -X POST http://localhost:4301/api/v1/apps/{APP_ID}/extensions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "extension_id": "my-widget", "target": "dashboard.widget", ... }'

# Deploy (makes extensions live)
curl -X POST http://localhost:4301/api/v1/apps/{APP_ID}/extensions/deploy \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "extensions": [...], "message": "v1" }'

API Reference

Exports

const {
  seloraxAuth,          // Express middleware
  seloraxApi,           // Platform API client
  createWebhookRouter,  // Webhook router factory
  verifyWebhook,        // Manual HMAC verification
  tokenStore,           // Token storage
  EXTENSION_TARGETS,    // Extension target constants
  EXTENSION_COMPONENTS, // Valid component types
  EXTENSION_ACTIONS,    // Valid action types
} = require('selorax-app-sdk');

seloraxAuth

| Aspect | Details | |:-------|:--------| | Type | Express middleware (req, res, next) | | Reads | X-Session-Token header | | Sets | req.session{ store_id: number, installation_id: number, app_id: number } | | Errors | 401 — missing or invalid token · 500 — platform verification failed |

seloraxApi

createWebhookRouter(handlers)

| Param | Type | Description | |:------|:-----|:------------| | handlers | { [topic]: (event, req, res) => void } | Event topic → handler map |

Returns express.Router with route POST /receive.

verifyWebhook(rawBody, signature, timestamp, secret)

| Param | Type | Description | |:------|:-----|:------------| | rawBody | string | Raw HTTP request body | | signature | string | X-SeloraX-Signature header value | | timestamp | string | X-SeloraX-Timestamp header value | | secret | string | Your webhook signing secret |

Returns booleantrue if valid signature and timestamp within 5 minutes.

tokenStore

| Method | Returns | Description | |:-------|:--------|:------------| | .get(storeId) | object \| null | Retrieve stored tokens | | .set(storeId, data) | void | Store token data | | .remove(storeId) | void | Delete stored tokens | | .getAll() | array | List all stored token objects |


Full Example

require('dotenv').config();
const express = require('express');
const {
  seloraxAuth,
  seloraxApi,
  createWebhookRouter,
  tokenStore,
} = require('selorax-app-sdk');

const app = express();

// ── Raw body capture (required for webhook HMAC) ───────────
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf.toString(); },
}));

// ── OAuth Install Callback ─────────────────────────────────
app.post('/oauth/callback', async (req, res) => {
  const { code } = req.body;

  const tokenRes = await fetch(`${process.env.SELORAX_API_URL}/apps/oauth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      client_id: process.env.SELORAX_CLIENT_ID,
      client_secret: process.env.SELORAX_CLIENT_SECRET,
      code,
      redirect_uri: 'http://localhost:5010/oauth/callback',
    }),
  });

  const data = await tokenRes.json();
  tokenStore.set(data.data.store_id, data.data);
  console.log(`Installed on store ${data.data.store_id}`);
  res.json({ message: 'Installed', status: 200 });
});

// ── Webhooks ───────────────────────────────────────────────
app.use('/webhooks', createWebhookRouter({
  'order.status_changed': (event, req, res) => {
    console.log(`[Store ${event.store_id}] Order #${event.data.order_id} → ${event.data.status}`);
    res.json({ message: 'OK', status: 200 });
  },

  'app.uninstalled': (event, req, res) => {
    tokenStore.remove(event.store_id);
    console.log(`Uninstalled from store ${event.store_id}`);
    res.json({ message: 'OK', status: 200 });
  },
}));

// ── Protected App Routes ───────────────────────────────────
app.get('/api/dashboard', seloraxAuth, async (req, res) => {
  const { store_id } = req.session;

  const [store, orders, wallet] = await Promise.all([
    seloraxApi.store.get(store_id),
    seloraxApi.orders.list(store_id, { limit: 5 }),
    seloraxApi.billing.getWallet(store_id),
  ]);

  res.json({
    store: store.data,
    recent_orders: orders.data,
    wallet_balance: wallet.data?.balance,
  });
});

// ── Start ──────────────────────────────────────────────────
app.listen(5010, () => console.log('App running on http://localhost:5010'));

Architecture

┌──────────────────────────────────────────────────────────────────┐
│                     SeloraX Merchant Dashboard                   │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                    Your App (iframe)                        │  │
│  │                                                            │  │
│  │   Frontend (Next.js)              Backend (Express)        │  │
│  │   ┌──────────────┐               ┌──────────────────┐     │  │
│  │   │              │   HTTP +      │                  │     │  │
│  │   │  App Bridge  │   X-Session   │  seloraxAuth     │     │  │
│  │   │  handles     │──────────────>│  verifies token  │     │  │
│  │   │  postMessage │   Token       │       │          │     │  │
│  │   │  tokens      │               │       ▼          │     │  │
│  │   │              │               │  seloraxApi      │     │  │
│  │   └──────────────┘               │  calls platform ─┼─────┼──┼──> SeloraX
│  │                                  │                  │     │  │    Platform
│  │                                  │  Webhook Router  │<────┼──┼──  API
│  │                                  │  verifies HMAC   │     │  │
│  │                                  └──────────────────┘     │  │
│  └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

Data flow:

| Step | What happens | |:----:|:-------------| | 1 | Merchant opens your app in the dashboard → iframe loads | | 2 | Dashboard sends a session token to the iframe via postMessage | | 3 | Frontend makes API calls to your backend with X-Session-Token | | 4 | seloraxAuth verifies the token → populates req.session | | 5 | Your backend calls seloraxApi to read/write platform data | | 6 | Platform sends webhook events (order changes, installs) to your backend | | 7 | createWebhookRouter verifies HMAC signature → routes to your handler |


Troubleshooting

Your .env file is missing credentials. Get them from the Developer Portal. Make sure you're calling require('dotenv').config() before importing the SDK.

Session tokens expire after 10 minutes. Your frontend should automatically request fresh tokens from the dashboard via postMessage. If testing manually, you'll need to get a new token from the dashboard.

Neither verification method is available. You need at least one of:

  • SESSION_SIGNING_KEY set in .env (for local verification), or
  • SELORAX_CLIENT_ID + SELORAX_CLIENT_SECRET + SELORAX_API_URL all set (for platform verification)

Three things to check:

  1. WEBHOOK_SIGNING_SECRET matches the secret shown in the Developer Portal
  2. You have the raw body capture middleware before your routes:
    app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString(); } }));
  3. The request body hasn't been modified between capture and verification

The SDK rejects webhooks older than 5 minutes. This usually means your server's clock is off. Fix with:

# Linux
sudo ntpdate pool.ntp.org

# macOS (usually auto-synced)
sudo sntp -sS pool.ntp.org

Your app doesn't have the required permission scope. Check the scopes you requested during app registration on the Developer Portal. Common scopes: read_orders, read_products, read_customers, read_inventory.


Links

| Resource | URL | |:---------|:----| | Documentation | docs.selorax.io | | Developer Portal | portal.selorax.io | | npm SDK | @selorax/cli | | npm UI Kit | @selorax/ui | | App Boilerplate | github.com/SeloraX-io/selorax-app-boilerplate | | SeloraX Platform | selorax.io |


License

MIT