Tutorial: Building SeloraX Messaging
Tutorial: Building SeloraX Messaging
This tutorial walks through the SeloraX Messaging app -- a production app that sends automated SMS messages to customers when order statuses change. It demonstrates every major platform feature: OAuth installation, iframe embedding, wallet billing, webhooks, and the full app lifecycle.
By the end, you will understand how all the pieces of the SeloraX app platform fit together.
What the App Does
When a merchant installs SeloraX Messaging:
- The merchant configures SMS templates for each order status (confirmed, processing, shipped, delivered)
- When an order's status changes, the platform fires a webhook to the app
- The app matches the status to a template, substitutes variables, debits the merchant's wallet, and sends the SMS
Architecture Overview
+-------------------+ postMessage +---------------------+
| SeloraX Dashboard |<-------------------->| Messaging Frontend |
| (parent window) | App Bridge | (React, iframe) |
+-------------------+ +---------------------+
| |
| REST API | REST API
v v
+-------------------+ +---------------------+
| SeloraX Platform |--- webhooks -------->| Messaging Backend |
| (Express API) | (Inngest) | (Express server) |
+-------------------+ +---------------------+
|
| HTTP
v
+---------------------+
| BulkSMS API |
| (SMS delivery) |
+---------------------+
Components:
- Frontend: React app embedded as an iframe in the merchant dashboard. Handles template management UI.
- Backend: Express server handling OAuth callbacks, webhook processing, and SMS delivery.
- Billing: Wallet-based model. Merchants top up their wallet; each SMS debits the balance.
Step 1: App Registration
Register your app on the SeloraX platform with the following configuration:
| Field | Value |
|---|---|
| Name | SeloraX Messaging |
| Slug | selorax-messaging |
| Scopes | read:orders, read:customers, read:store, billing, manage:messaging |
| Webhook topics | order.status_changed |
app_url | Frontend iframe URL (e.g., https://messaging.selorax.com) |
webhook_url | Backend OAuth endpoint (e.g., https://messaging-api.selorax.com/api/messaging/oauth) |
The platform assigns your app a client_id, client_secret, and session_signing_key. Store these securely.
Step 2: OAuth Installation
When a merchant clicks Install in the SeloraX marketplace:
- The dashboard initiates a direct-install flow (no redirect-based OAuth dance needed for first-party apps)
- The platform delivers
access_tokenandrefresh_tokento yourwebhook_url - Your backend stores these tokens associated with the
store_id - The platform automatically creates webhook subscriptions for the topics your app declared (
order.status_changed)
// Your OAuth callback handler
router.post('/oauth', async (req, res) => {
const { store_id, access_token, refresh_token, installation_id } = req.body;
// Store credentials for this merchant
await db.query(
'INSERT INTO installations (store_id, access_token, refresh_token, installation_id) VALUES (?, ?, ?, ?)',
[store_id, access_token, refresh_token, installation_id]
);
res.status(200).json({ message: 'Installation successful' });
});Step 3: Iframe Embed
Once installed, the merchant accesses your app from their dashboard. The embedding flow uses the App Bridge and Session Token Flow:
- Dashboard fetches embed params from the platform (HMAC-signed iframe URL + session token)
- Dashboard loads your app in an iframe
- Your app verifies the HMAC parameters
- Your app sends
app-bridge:ready - Dashboard responds with
selorax:session-token - Your app initializes with the token and displays the template management UI
// In your React app's entry point
import { SeloraXAppBridge } from './app-bridge';
const bridge = new SeloraXAppBridge();
bridge.on('token', (token) => {
// Store token, initialize app state
localStorage.setItem('session_token', token);
fetchTemplates(token);
});
bridge.ready();Step 4: Wallet Setup
SeloraX Messaging uses wallet-based billing. The merchant pre-loads a balance, and each SMS debits from it.
Creating a Top-Up Charge
When the merchant clicks "Top Up Wallet" in your app:
// App backend creates a wallet top-up charge via client credentials
const response = await fetch('https://api.selorax.io/api/apps/v1/billing/wallet-topup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Id': process.env.SELORAX_CLIENT_ID,
'X-Client-Secret': process.env.SELORAX_CLIENT_SECRET,
'X-Store-Id': String(storeId),
},
body: JSON.stringify({
amount: 500.00,
currency: 'BDT',
return_url: `${process.env.APP_URL}/wallet/success`,
}),
});
const result = await response.json();
const { charge_id } = result.data;Redirecting to Payment
Use the App Bridge to navigate the merchant to the charge approval page:
bridge.billingRedirect(`/${storeId}/settings/apps/billing/${chargeId}`);The merchant pays via EPS (Electronic Payment System). On successful payment, the wallet is credited automatically.
Checking Balance
Before sending an SMS, check the wallet balance:
const balanceRes = await fetch('https://api.selorax.io/api/apps/v1/billing/wallet', {
headers: {
'X-Client-Id': process.env.SELORAX_CLIENT_ID,
'X-Client-Secret': process.env.SELORAX_CLIENT_SECRET,
'X-Store-Id': String(storeId),
},
});
const result = await balanceRes.json();
const { balance } = result.data;Step 5: Template Configuration
The app's iframe UI lets merchants create SMS templates with placeholder variables:
Available template variables:
| Variable | Description | Example |
|---|---|---|
{{order_number}} | The order's display number | ORD-1234 |
{{customer_name}} | Customer's full name | John Doe |
{{customer_phone}} | Customer's phone number | +8801712345678 |
{{tracking_id}} | Shipping tracking ID | TRACK123 |
{{store_name}} | Merchant's store name | My Shop |
{{status}} | New order status | confirmed |
Example templates by status:
- Confirmed:
Hi {{customer_name}}, your order #{{order_number}} has been confirmed! We will process it shortly. - Processing:
Hi {{customer_name}}, your order #{{order_number}} is being prepared. - Shipped:
Hi {{customer_name}}, your order #{{order_number}} has been shipped! Tracking: {{tracking_id}} - Delivered:
Hi {{customer_name}}, your order #{{order_number}} has been delivered. Thank you for shopping with {{store_name}}!
Templates are stored in the messaging app's own database, mapped to order statuses.
Step 6: Webhook-Triggered SMS
This is the core of the app. When an order's status changes, the following chain executes:
6.1 Platform Emits Event
When a merchant (or the system) changes an order's status, the platform calls publishPlatformEvent() with the topic order.status_changed.
6.2 Inngest Delivers Webhook
Inngest picks up the event and fans it out to all apps subscribed to order.status_changed for that store. The webhook is delivered as an HMAC-signed HTTP POST to your app's webhook endpoint.
6.3 App Receives and Verifies
router.post('/webhooks', express.raw({ type: 'application/json' }), async (req, res) => {
// Verify HMAC signature (signs timestamp.body to prevent replay attacks)
const signature = req.headers['x-selorax-signature'];
const timestamp = req.headers['x-selorax-timestamp'];
const isValid = verifySignature(req.body, signature, timestamp, WEBHOOK_SECRET);
if (!isValid) return res.status(401).send('Invalid signature');
const event = JSON.parse(req.body);
// Process...
});6.4 Status Mapping
The platform emits a generic order.status_changed event. Your app maps the order's new status to a specific event topic using STATUS_TO_EVENT_TOPIC:
| Order Status | Mapped Topic |
|---|---|
| confirmed | order.confirmed |
| processing | order.processing |
| shipped | order.shipped |
| delivered | order.delivered |
6.5 Template Rendering and SMS Delivery
The app:
- Looks up the merchant's template for the mapped status
- Substitutes variables using the event payload (which includes
customer_name,customer_phone,order_number,tracking_id,store_nameviabuildOrderEventPayload()) - Checks the merchant's wallet balance
- Debits the wallet for the SMS cost
- Sends the SMS via the BulkSMS API
See the Webhook to SMS guide for the complete code implementation.
Step 7: Ongoing Operations
Token Refresh
Server-to-server API calls use client_id + client_secret (these never expire, similar to Shopify offline tokens). Session tokens for the iframe are refreshed via the App Bridge on 401.
Wallet Low-Balance Alerts
Monitor wallet balance and alert merchants when it drops below a threshold:
if (balance < 50.00) {
// Show low-balance warning in the iframe UI
// Optionally send a notification to the merchant
}SMS Delivery Logging
Log every SMS attempt with status, cost, and timestamp for merchant visibility:
await db.query(
'INSERT INTO sms_logs (store_id, order_id, phone, status, cost, sent_at) VALUES (?, ?, ?, ?, ?, NOW())',
[storeId, orderId, phone, 'sent', 2.50]
);Step 8: Production Deployment
When your app is working locally, deploy it to production:
Backend Environment Variables
Set these on your backend server (e.g., DigitalOcean App Platform, Railway, Fly.io):
# App credentials (from Developer Portal)
SELORAX_CLIENT_ID=sx_app_...
SELORAX_CLIENT_SECRET=sx_secret_...
SESSION_SIGNING_KEY=... # 64 hex chars, from Developer Portal
# Platform API
SELORAX_API_URL=https://api.selorax.io/api
# Database (your app's own database)
DATABASE_URL=mysql://user:pass@host:port/dbname
# SMS provider
SMS_API_KEY=your_sms_api_key
SMS_API_ENDPOINT=http://bulksmsbd.net/api/smsapi
# Server
PORT=5002
NODE_ENV=production
JWT_SECRET=your_jwt_secretFrontend Environment Variables
Set these in your frontend deployment (e.g., Vercel):
NEXT_PUBLIC_MESSAGING_API_URL=https://your-backend.example.com/api/messagingUpdate App URLs in Developer Portal
After deploying, update your app's URLs via the Developer Portal:
- Set
app_urlto your production frontend URL (e.g.,https://messaging.selorax.com) - Set
webhook_urlto your production backend OAuth endpoint (e.g.,https://messaging-api.selorax.com/api/messaging/oauth) - Set
webhook_receive_urlto your production webhook endpoint (e.g.,https://messaging-api.selorax.com/api/messaging/webhooks)
Database Setup
- Ensure
requested_scopesincludesbilling(required for wallet operations):-- Check current scopes SELECT requested_scopes FROM apps WHERE slug = 'your-app-slug'; - After installation, verify
granted_scopesinapp_installationsalso includesbilling.
Verify Production
# 1. Check backend health
curl https://your-backend.example.com/api/messaging/health
# 2. Check wallet endpoint (requires valid session token)
# This should return the wallet balance, not 403 or 500
# 3. Install on a test store and verify:
# - Iframe loads and shows "Connected"
# - Wallet balance displays correctly
# - Top Up button appears
# - Templates save and loadCommon production issues
- Wallet/Top Up not showing: Missing
billingscope inrequested_scopesorgranted_scopes. Add it via SQL and reinstall. - Iframe shows "Connecting...": Wrong
app_urlorDASHBOARD_URLmismatch. Verify both point to correct production URLs. - Webhook delivery fails: Wrong
webhook_receive_url. Update via Developer Portal and verify with the webhook test endpoint.
Recap
This tutorial covered the full lifecycle of a SeloraX embedded app:
| Step | Platform Feature |
|---|---|
| Registration | App manifest (scopes, topics, URLs) via Developer Portal |
| Installation | OAuth direct-install, token delivery |
| Embedding | App Bridge, session tokens, HMAC-signed URLs |
| Billing | Wallet top-up via EPS, per-action debits |
| Configuration | Iframe UI for merchant settings |
| Automation | Webhook events via Inngest, HMAC verification |
| Delivery | External API integration (BulkSMS) |
| Deployment | Backend + frontend deployment, URL configuration |
Every SeloraX app follows this same pattern. The specifics of what your app does in steps 5-6 will differ, but the platform integration points remain the same.