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 🙏

© 2025 – Pkg Stats / Ryan Hefner

advanced-ussd-builder

v2.4.3

Published

Advanced USSD Menu Builder with persistent state and navigation

Readme

Advanced USSD Builder

npm version npm downloads license typescript

A powerful TypeScript library for building interactive USSD menus with persistent state management

InstallationQuick StartDocumentationExamplesAPI

📋 Table of Contents

✨ Features

  • 🚀 Multi-Provider Support - Works with MTN, Nalo, Telecel, Airtel-Tigo, Cross-Switch, and Custom providers
  • 🔐 Built-in Authentication - PIN-based authentication with customizable handlers
  • 🔌 Middleware Support - Request/response interceptors for logging, validation, and transformation
  • 🎨 Custom Provider Setup - Easy integration with any USSD gateway through flexible configuration
  • 🔀 Advanced Proxy Support - Forward requests to external USSD services with automatic network detection, flexible request formats (telco-specific or normalized), and custom transformations
  • 💾 Persistent State Management - Redis-powered session management for stateful interactions
  • 📱 Dynamic Menu Generation - Create menus programmatically with render and handler functions
  • 🔄 Navigation History - Track and navigate through menu paths seamlessly
  • 🎯 TypeScript Support - Full type definitions for better development experience
  • High Performance - Optimized for speed with minified production builds
  • 🔧 Flexible Configuration - Customizable session prefixes, navigation keys, and more

📦 Installation

npm install advanced-ussd-builder

or using yarn:

yarn add advanced-ussd-builder

Prerequisites

  • Node.js >= 14.x
  • Redis server (for session management)
  • TypeScript (for development)

🚀 Quick Start

import { UssdBuilder, UssdMenu, iUssdMenu, iUssdMenuOptions, iMTNUSSDArgs } from 'advanced-ussd-builder';

// Define menu configuration
const menuOptions: iUssdMenuOptions = {
  provider: 'mtn',
  require_pin: false,
  service_code: '*123#',
  next_page_key: '#',
  back_page_key: '0',
  redis_connection_url: 'redis://localhost:6379',
  state: {
    user_id: '',
    session_data: {}
  }
};

// Create handler menus with path-based handlers
const registrationMenu: iUssdMenu = {
  title: 'Register',
  type: 'handler',
  handler_type: 'path',
  handler_name_in_destination: 'root_menu',
  handler: path.join(__dirname, '/handlers/registration.js')
};

// Create handler menus with function handlers
const balanceMenu: iUssdMenu = {
  title: 'Check Balance',
  type: 'handler',
  handler_type: 'function',
  handler: async (args) => {
    const balance = await getUserBalance(args.msisdn);
    return `Your balance is $${balance}`;
  }
};

// Create menu array
const menus = [registrationMenu, balanceMenu];

// Initialize USSD Builder with root menu
const ussdBuilder = new UssdBuilder(
  menuOptions,
  new UssdMenu('Welcome to MyApp', menus)
);

// Process USSD request (Express.js example)
app.post('/ussd/mtn', async (req, res) => {
  const ussdArgs = req.body as iMTNUSSDArgs;
  
  const sendResponse = (response) => {
    return res.json(response);
  };
  
  await ussdBuilder.pipe<'mtn'>(ussdArgs, sendResponse);
});

🌐 Provider Support

Advanced USSD Builder supports multiple telecom providers out of the box:

| Provider | Status | Request Interface | Response Interface | |----------|--------|------------------|-------------------| | MTN | ✅ Supported | iMTNUSSDArgs | iMTNUssdCallback | | Nalo | ✅ Supported | iNaloUSSDArgs | iNaloUssdCallback | | Telecel | ✅ Supported | iTelecelUssdArgs | iTelecelUssdCallBack | | Airtel-Tigo | ✅ Supported | iAirtelTigoUssdArgs | iAirtelTigoUssdCallBack | | Cross-Switch | ✅ Supported | iCrossSwitchUssdArgs | iCrossSwitchUssdCallBack | | Custom | ✅ Supported | Custom Interface | Custom Response |

📚 Core Concepts

UssdMenu

The UssdMenu class represents a menu container that can hold multiple menu items. Each menu item follows the iUssdMenu interface structure.

// Create a root menu with multiple options
const menuItems: iUssdMenu[] = [
  {
    title: 'Option 1',
    type: 'handler',
    handler_type: 'function',
    handler: async (args) => {
      return 'Response for option 1';
    }
  },
  {
    title: 'Option 2',
    type: 'handler',
    handler_type: 'path',
    handler_name_in_destination: 'default',
    handler: './handlers/option2.js'
  }
];

const rootMenu = new UssdMenu('Select an option:', menuItems);

UssdBuilder

The main orchestrator that manages menus, sessions, and request processing. It requires both configuration options and a root menu.

const options: iUssdMenuOptions = {
  provider: 'mtn', // or 'nalo', 'telecel', 'airtel-tigo', 'cross-switch', 'custom'
  require_pin: false,
  service_code: '*123#',
  next_page_key: '#',
  back_page_key: '0',
  redis_connection_url: 'redis://localhost:6379',
  state: {}, // Custom state object
  middleware: async (args) => {
    // Optional middleware
  },
  authentication_handler: async (args) => {
    // Optional authentication
    return true;
  }
};

const builder = new UssdBuilder(options, rootMenu);

Session Management

Sessions are automatically managed using Redis with the following structure:

  • Session Key: {prefix}:{session_id}
  • Session Data: Stores navigation path, current menu, and custom data
  • TTL: Configurable time-to-live for session expiration

💡 Usage Examples

Render Menu (Display Options)

// Render menu displays a list of options to the user
const renderMenus: iUssdMenu[] = [
  {
    title: 'Account Services',
    type: 'render', // This will display as an option
    render: {
      success_message: 'Select an option:',
      error_message: 'Invalid selection'
    }
  },
  {
    title: 'Transfer Money',
    type: 'render',
    render: {
      success_message: 'Choose transfer type:'
    }
  },
  {
    title: 'Buy Airtime',
    type: 'render',
    render: {
      success_message: 'Select amount:'
    }
  }
];

const ussdBuilder = new UssdBuilder(
  options,
  new UssdMenu('Main Menu\n1. Account Services\n2. Transfer Money\n3. Buy Airtime', renderMenus)
);

Render Menu with Function Handler

// When type is 'render' with a handler, the handler controls the response
const dynamicRenderMenu: iUssdMenu = {
  title: 'Account List',
  type: 'render',
  handler_type: 'function',
  handler: async (args) => {
    // Dynamically generate menu options
    const accounts = await getUserAccounts(args.msisdn);
    const menuOptions = accounts.map((acc, idx) => 
      `${idx + 1}. ${acc.name} - ${acc.balance}`
    ).join('\n');
    
    // Handler returns the complete response
    return {
      message: `Your Accounts:\n${menuOptions}\n\nSelect account:`,
      continue_session: true
    };
  }
  // No render object needed when handler is provided
};

Basic Menu

const bankingMenus: iUssdMenu[] = [
  {
    title: 'Check Balance',
    type: 'handler',
    handler_type: 'function',
    handler: async (args) => {
      const balance = await getBalance(args.msisdn);
      return `Your balance: $${balance}`;
    }
  },
  {
    title: 'Transfer Funds',
    type: 'handler',
    handler_type: 'path',
    handler_name_in_destination: 'root_menu',
    handler: './handlers/transfer.js'
  },
  {
    title: 'Mini Statement',
    type: 'handler',
    handler_type: 'function',
    handler: async (args) => {
      const statement = await getStatement(args.msisdn);
      return statement;
    }
  }
];

const ussdBuilder = new UssdBuilder(
  options,
  new UssdMenu('Welcome to Banking Services', bankingMenus)
);

Dynamic Menus

// Dynamically create menus based on user data
const createDynamicMenu = async (userPhone: string) => {
  const user = await getUserData(userPhone);
  const menuItems: iUssdMenu[] = [];
  
  // Add different menus based on user status
  if (user.isRegistered) {
    menuItems.push({
      title: 'My Account',
      type: 'handler',
      handler_type: 'path',
      handler_name_in_destination: 'root_menu',
      handler: './handlers/account.js'
    });
  } else {
    menuItems.push({
      title: 'Register',
      type: 'handler',
      handler_type: 'path',
      handler_name_in_destination: 'root_menu',
      handler: './handlers/registration.js'
    });
  }
  
  return new UssdMenu('Welcome', menuItems);
};

// Use in request handler
app.post('/ussd', async (req, res) => {
  const rootMenu = await createDynamicMenu(req.body.msisdn);
  const builder = new UssdBuilder(options, rootMenu);
  await builder.pipe<'mtn'>(req.body, (response) => res.json(response));
});

Proxy Menu (External Service Integration)

New in v2.4+: Enhanced proxy support with automatic network detection and flexible request formats.

Basic Proxy Configuration

// Simple proxy to external service
const basicProxy: iUssdMenu = {
  title: 'Loan Services',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://loan-service.example.com/ussd',
    timeout: 25000,
    headers: {
      'API-Key': process.env.LOAN_API_KEY
    }
  }
};

Telco-Specific Endpoints (Default Behavior)

// Forward requests in original telco format to telco-specific endpoints
const telcoSpecificProxy: iUssdMenu = {
  title: 'Payment Service',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://payment.example.com/v1/ussd/mtn', // MTN-specific endpoint
    // forward_original_format: true (default)
    // Forwards MTN request format directly to the endpoint
  }
};

Unified Endpoint with Network Detection

// Use single endpoint for all networks with automatic network detection
const unifiedProxy: iUssdMenu = {
  title: 'Unified Service',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://api.example.com/ussd', // Single endpoint for all networks
    forward_original_format: false, // Use normalized format
    // Network is automatically detected from phone number
  }
};

// The proxy sends normalized request with network field:
// {
//   msisdn: '0241234567',
//   session_id: 'session-123',
//   user_input: '1',
//   service_code: '*123#',
//   is_new_request: false,
//   provider: 'mtn',
//   network: 'MTN', // Auto-detected from phone number prefix
//   session_data: {...},
//   original_request: {...}
// }

Advanced Proxy with Transformations

const advancedProxy: iUssdMenu = {
  title: 'Insurance Portal',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://insurance.example.com/ussd',
    forward_original_format: false,
    timeout: 20000,
    retry_attempts: 2,
    retry_delay: 1500,
    session_bridge: true, // Share session state
    headers: {
      'Authorization': `Bearer ${process.env.TOKEN}`,
      'X-Partner-ID': 'partner-123'
    },
    // Custom request transformation
    transform_request: (data) => ({
      phone: data.msisdn,
      network: data.network, // Includes auto-detected network
      session: data.session_id,
      input: data.user_input,
      metadata: {
        provider: data.provider,
        timestamp: Date.now()
      }
    }),
    // Custom response transformation
    transform_response: (response) => ({
      message: response.text,
      require_feedback: response.continue
    })
  }
};

Custom Proxy Handler

// Use custom function instead of HTTP request
const customProxy: iUssdMenu = {
  title: 'Custom Service',
  type: 'proxy',
  proxy_config: {
    proxy_handler: async (builder, user_input) => {
      // Custom logic here
      const result = await customServiceCall(builder.session, user_input);
      return {
        message: result.display,
        require_feedback: result.needsInput
      };
    }
  }
};

Network Detection

The library automatically detects the network from Ghana phone numbers:

  • MTN: 024, 054, 055, 059, 025
  • Telecel: 020, 050
  • AirtelTigo: 026, 056, 027, 057, 028
// Network detection works with various formats
getNetworkFromPhoneNumber('0241234567');     // Returns: 'MTN'
getNetworkFromPhoneNumber('233241234567');   // Returns: 'MTN'
getNetworkFromPhoneNumber('+233241234567');  // Returns: 'MTN'
getNetworkFromPhoneNumber('0201234567');     // Returns: 'TELECEL'
getNetworkFromPhoneNumber('0561234567');     // Returns: 'AIRTEL-TIGO'

// Combine local and proxy menus const mainMenus: iUssdMenu[] = [ { title: 'My Account', type: 'handler', handler_type: 'path', handler: './handlers/account.js' }, ...proxyMenus // External services appear as regular menu items ];

const ussdBuilder = new UssdBuilder( options, new UssdMenu('Select Service:', mainMenus) );


### Handler Menus

```typescript
// Simple handler
const balanceHandler = new UssdMenu('check_balance', async (args) => {
  const balance = await getBalance(args.msisdn);
  return `Your balance: $${balance}\n\nPress 0 to go back`;
});

// Handler with module path
const complexHandler = new UssdMenu('process_transfer', './handlers/transfer.js');

builder.register_menu(balanceHandler);
builder.register_menu(complexHandler);

Paginated Menus

Pagination is automatic when menu items exceed the configured limit:

const builder = new UssdBuilder({
  redis_url: 'redis://localhost:6379',
  max_menu_items: 5 // Show 5 items per page
});

const largeMenu = new UssdMenu('products', 'Select Product');
// Adding more than 5 items will trigger pagination
for (let i = 1; i <= 20; i++) {
  largeMenu.add_child(`product_${i}`, `Product ${i}`);
}

📖 API Reference

UssdBuilder

Constructor Options

interface UssdBuilderOptions {
  redis_url: string;           // Redis connection URL
  session_prefix?: string;     // Session key prefix for multi-instance isolation (default: 'ussd')
  session_ttl?: number;        // Session TTL in seconds (default: 120 seconds / 2 minutes)
  max_menu_items?: number;     // Max items per page (default: 5)
  end_session_text?: string;   // Custom end session message
}

Methods

| Method | Description | Parameters | Returns | |--------|-------------|------------|---------| | pipe<T>(args, callback) | Process USSD request | args: provider args, callback: response handler | Promise | | getSession() | Get current session | - | iUssdSession | | saveSession() | Save session to Redis | - | Promise | | endSession() | End current session | - | Promise |

UssdMenu

Constructor

new UssdMenu(
  id: string,
  title: string | Function | undefined,
  opts?: {
    custom_input?: boolean;
    module_path?: string;
  }
)

Menu Item Types

| Property | Description | Type | |----------|-------------|------| | title | Menu item display text | string | | type | Menu type | 'render' | 'handler' | 'proxy' | | handler_type | Handler type (for handler menus) | 'path' | 'function' | | handler | Handler implementation | string | Function | | handler_name_in_destination | Exported function name in module | string | | proxy_config | Proxy configuration (for proxy menus) | ProxyConfig object |

🔌 Provider Integration

MTN

app.post('/ussd/mtn', async (req, res) => {
  const ussdArgs = req.body as iMTNUSSDArgs;
  
  // Response callback
  const sendResponse = <iMTNUssdCallback>(response: iMTNUssdCallback) => {
    return res.json(response);
  };
  
  // Configure and build menu
  const options: iUssdMenuOptions = {
    provider: 'mtn',
    service_code: '*123#',
    next_page_key: '#',
    back_page_key: '0',
    require_pin: false,
    redis_connection_url: REDIS_URL,
    state: { /* custom state */ }
  };
  
  const menus: iUssdMenu[] = [/* your menus */];
  const ussdBuilder = new UssdBuilder(
    options,
    new UssdMenu('Welcome', menus)
  );
  
  await ussdBuilder.pipe<'mtn'>(ussdArgs, sendResponse);
});

Nalo

app.post('/ussd/nalo', async (req, res) => {
  const ussdArgs = req.body as iNaloUSSDArgs;
  
  const sendResponse = <iNaloUssdCallback>(response: iNaloUssdCallback) => {
    return res.json(response);
  };
  
  const options: iUssdMenuOptions = {
    provider: 'nalo',
    service_code: '*123#',
    next_page_key: '#',
    back_page_key: '0',
    require_pin: false,
    redis_connection_url: REDIS_URL
  };
  
  const ussdBuilder = new UssdBuilder(options, rootMenu);
  await ussdBuilder.pipe<'nalo'>(ussdArgs, sendResponse);
});

Telecel

app.post('/ussd/telecel', async (req, res) => {
  const ussdArgs = req.body as iTelecelUssdArgs;
  
  const sendResponse = <iTelecelUssdCallBack>(response: iTelecelUssdCallBack) => {
    return res.json(response);
  };
  
  const options: iUssdMenuOptions = {
    provider: 'telecel',
    service_code: '*123#',
    next_page_key: '#',
    back_page_key: '0',
    require_pin: false,
    redis_connection_url: REDIS_URL
  };
  
  const ussdBuilder = new UssdBuilder(options, rootMenu);
  await ussdBuilder.pipe<'telecel'>(ussdArgs, sendResponse);
});

Airtel-Tigo

app.post('/ussd/at', async (req, res) => {
  const ussdArgs = req.body as iAirtelTigoUssdArgs;
  
  const sendResponse = <iAirtelTigoUssdCallBack>(response: iAirtelTigoUssdCallBack) => {
    return res.json(response);
  };
  
  const options: iUssdMenuOptions = {
    provider: 'airtel-tigo',
    service_code: '*123#',
    next_page_key: '#',
    back_page_key: '0',
    require_pin: false,
    redis_connection_url: REDIS_URL
  };
  
  const ussdBuilder = new UssdBuilder(options, rootMenu);
  await ussdBuilder.pipe<'airtel-tigo'>(ussdArgs, sendResponse);
});

Cross-Switch

app.post('/ussd/cross-switch', async (req, res) => {
  const ussdArgs = req.body as iCrossSwitchUssdArgs;
  
  const sendResponse = <iCrossSwitchUssdCallBack>(response: iCrossSwitchUssdCallBack) => {
    return res.json(response);
  };
  
  const options: iUssdMenuOptions = {
    provider: 'cross-switch',
    service_code: '*123#',
    next_page_key: '#',
    back_page_key: '0',
    require_pin: false,
    redis_connection_url: REDIS_URL
  };
  
  const ussdBuilder = new UssdBuilder(options, rootMenu);
  await ussdBuilder.pipe<'cross-switch'>(ussdArgs, sendResponse);
});

Custom Provider

The library supports custom providers for any USSD gateway not listed above:

// Custom provider implementation
app.post('/ussd/custom', async (req, res) => {
  const customArgs = {
    msisdn: req.body.phoneNumber,
    session_id: req.body.sessionId,
    msg: req.body.userInput,
    // Map your custom fields
  };
  
  const sendResponse = (response) => {
    // Transform response to your custom format
    const customResponse = {
      text: response.message || response.msg,
      continueSession: response.msgtype !== false,
      sessionId: response.session_id
    };
    return res.json(customResponse);
  };
  
  const options: iUssdMenuOptions = {
    provider: 'custom',
    service_code: req.body.serviceCode,
    next_page_key: '#',
    back_page_key: '0',
    require_pin: false,
    redis_connection_url: REDIS_URL,
    make_provider_response: false, // Handle response formatting manually
    state: {}
  };
  
  const menus: iUssdMenu[] = [/* your menus */];
  const ussdBuilder = new UssdBuilder(
    options,
    new UssdMenu('Welcome', menus)
  );
  
  await ussdBuilder.pipe<'custom'>(customArgs, sendResponse);
});

🚀 Advanced Features

Session State Management

// Pass state through options that persists across menus
const options: iUssdMenuOptions = {
  provider: 'mtn',
  service_code: '*123#',
  next_page_key: '#',
  back_page_key: '0',
  require_pin: false,
  redis_connection_url: REDIS_URL,
  state: {
    user_id: '12345',
    user_name: 'John Doe',
    account_type: 'premium',
    // Any custom data you need
  }
};

// Access state in handlers
const menuHandler: iUssdMenu = {
  title: 'Account Info',
  type: 'handler',
  handler_type: 'function',
  handler: async (args) => {
    // Access state passed through options
    const userId = args.state?.user_id;
    const userName = args.state?.user_name;
    return `Welcome ${userName}, your ID is ${userId}`;
  }
};

Middleware Support

const options: iUssdMenuOptions = {
  provider: 'mtn',
  service_code: '*123#',
  next_page_key: '#',
  back_page_key: '0',
  require_pin: false,
  redis_connection_url: REDIS_URL,
  middleware: async (args) => {
    // Log every request
    console.log('USSD Request:', args);
    
    // Validate user
    const user = await validateUser(args.msisdn);
    if (!user) {
      throw new Error('Unauthorized');
    }
    
    // Add user to args
    args.user = user;
  }
};

Authentication Handler

const options: iUssdMenuOptions = {
  provider: 'mtn',
  service_code: '*123#',
  next_page_key: '#',
  back_page_key: '0',
  require_pin: true, // Enable PIN requirement
  redis_connection_url: REDIS_URL,
  authentication_handler: async (args) => {
    // Custom authentication logic
    const isValid = await validateUserPin(args.msisdn, args.input);
    return isValid;
  }
};

Proxy Service Integration

Proxy Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | target_url | string | - | URL of the target USSD service (required unless using proxy_handler) | | forward_original_format | boolean | true | Forward telco-specific format or use normalized format | | timeout | number | 25000 | Request timeout in milliseconds | | retry_attempts | number | 1 | Number of retry attempts for failed requests | | retry_delay | number | 1000 | Delay between retries in milliseconds | | session_bridge | boolean | true | Share session state with target service | | headers | object | {} | Additional HTTP headers to send | | transform_request | function | - | Custom request transformation function | | transform_response | function | - | Custom response transformation function | | proxy_handler | function | - | Custom handler function instead of HTTP request |

Request Formats

1. Original Telco Format (default)
// Forwards the exact format from the telco
const telcoProxy: iUssdMenu = {
  title: 'Service',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://api.example.com/v1/ussd/mtn',
    forward_original_format: true // default
  }
};

// MTN format example:
// {
//   sessionId: 'session-123',
//   messageType: '1',
//   msisdn: '233241234567',
//   ussdString: '1',
//   serviceCode: '*123#',
//   cellId: 'cell-id',
//   imsi: 'imsi-id',
//   ...
// }
2. Normalized Format with Network Detection
// Sends normalized format with auto-detected network
const normalizedProxy: iUssdMenu = {
  title: 'Service',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://api.example.com/ussd', // Single endpoint
    forward_original_format: false
  }
};

// Normalized format:
// {
//   msisdn: '0241234567',
//   session_id: 'session-123',
//   user_input: '1',
//   service_code: '*123#',
//   is_new_request: false,
//   provider: 'mtn',
//   network: 'MTN', // Auto-detected from phone number
//   session_data: {...},
//   original_request: {...} // Original telco request
// }

Backend Implementation Examples

Single Endpoint with Network Routing
// Express.js example
app.post('/ussd', (req, res) => {
  const { network, msisdn, user_input, session_id } = req.body;
  
  // Route based on detected network
  switch(network) {
    case 'MTN':
      return handleMTN(req, res);
    case 'TELECEL':
      return handleTelecel(req, res);
    case 'AIRTEL-TIGO':
      return handleAirtelTigo(req, res);
    default:
      return handleUnknown(req, res);
  }
});
Separate Telco Endpoints
// MTN endpoint - receives MTN format
app.post('/v1/ussd/mtn', (req, res) => {
  const { sessionId, msisdn, ussdString, messageType } = req.body;
  // Handle MTN-specific format
});

// Telecel endpoint - receives Telecel format
app.post('/v1/ussd/telecel', (req, res) => {
  const { sessionid, msisdn, msg, type } = req.body;
  // Handle Telecel-specific format
});

XML Payload Support

The proxy handler automatically preserves XML payloads when forwarding requests to target endpoints. This is particularly important for Telecel and Airtel-Tigo providers which use XML formats.

XML Preservation Features
  • Automatic Detection: Content-type headers are automatically detected and preserved
  • Raw Body Forwarding: XML bodies are forwarded as-is without conversion to JSON
  • Provider Support: Full support for Telecel and Airtel-Tigo XML formats
  • Response Parsing: XML responses are properly parsed and converted to the expected format
Example: XML Provider Proxy
// Telecel XML format proxy
const telecelProxy: iUssdMenu = {
  title: 'Telecel Service',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://api.example.com/v1/ussd/telecel',
    forward_original_format: true // Preserves XML format
  }
};

// When receiving Telecel XML:
// <request>
//   <msisdn>233241234567</msisdn>
//   <sessionid>session-123</sessionid>
//   <type>1</type>
//   <msg>1</msg>
// </request>

// The XML is forwarded as-is to the target endpoint
Example: Airtel-Tigo XML Support
// Airtel-Tigo XML format proxy
const airtelTigoProxy: iUssdMenu = {
  title: 'Airtel-Tigo Service',
  type: 'proxy',
  proxy_config: {
    target_url: 'https://api.example.com/v1/ussd/airtel-tigo',
    forward_original_format: true // Preserves XML format
  }
};

// Airtel-Tigo XML with nested structure:
// <dataSet>
//   <param>
//     <name>sessionId</name>
//     <value>session-123</value>
//   </param>
//   <param>
//     <name>msisdn</name>
//     <value>233241234567</value>
//   </param>
// </dataSet>
Backend Handling for XML
// Express.js with XML body parser
const xmlParser = require('express-xml-bodyparser');

app.use(xmlParser());

app.post('/v1/ussd/telecel', (req, res) => {
  // req.body contains parsed XML
  const { msisdn, sessionid, msg, type } = req.body.request;
  
  // Process and return XML response
  res.set('Content-Type', 'text/xml');
  res.send(`
    <response>
      <message>Response message</message>
      <type>1</type>
    </response>
  `);
});

Use Cases

  • Third-party Integration: Connect to external USSD services (loans, insurance, payments)
  • Microservices Architecture: Route to different backend services
  • Partner Applications: Seamlessly integrate partner USSD applications
  • Load Balancing: Distribute requests across multiple backends
  • Network Portability: Handle users who port numbers between networks
  • Testing: Easy testing with different network configurations
  • XML Service Integration: Seamlessly integrate with XML-based USSD services
// Complete example with all features
const advancedProxyMenu: iUssdMenu = {
  title: 'Payment Gateway',
  type: 'proxy',
  proxy_config: {
    target_url: process.env.PAYMENT_API_URL,
    forward_original_format: false, // Use normalized format
    timeout: 25000,
    retry_attempts: 2,
    retry_delay: 1000,
    session_bridge: true,
    headers: {
      'Authorization': `Bearer ${process.env.AUTH_TOKEN}`,
      'X-Partner-ID': 'partner-123'
    },
    transform_request: (data) => ({
      phoneNumber: data.msisdn,
      network: data.network, // Includes auto-detected network
      sessionId: data.session_id,
      userInput: data.user_input,
      metadata: {
        provider: data.provider,
        detectedNetwork: data.network,
        timestamp: Date.now()
      }
    }),
    transform_response: (response) => ({
      message: response.display_text,
      require_feedback: response.await_input === true,
      session_data: response.session_state
    })
  }
};

Path-based Handler Modules

// menu-handler.js - External handler module
export const root_menu = async (args) => {
  // Handler logic
  const userData = await fetchUserData(args.msisdn);
  return `Welcome ${userData.name}`;
};

export const process_payment = async (args) => {
  // Another handler in same module
  return 'Payment processed';
};

// Usage in menu definition
const menus: iUssdMenu[] = [
  {
    title: 'User Info',
    type: 'handler',
    handler_type: 'path',
    handler_name_in_destination: 'root_menu', // Function to call
    handler: path.join(__dirname, '/handlers/menu-handler.js')
  },
  {
    title: 'Process Payment',
    type: 'handler',
    handler_type: 'path',
    handler_name_in_destination: 'process_payment', // Different function
    handler: path.join(__dirname, '/handlers/menu-handler.js')
  }
];

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

👤 Author

Mohammed Bashiru

🙏 Acknowledgments

  • Redis for reliable session management
  • TypeScript for type safety
  • Jest for testing framework
  • All contributors and users of this library

📞 Support


Made with ❤️ by the BashTech Solutions Team