agentsclimarketplace

Interswitch setup

Skill rexedge/interswitch/skills/interswitch-setup

18 AI agent skills for Interswitch API integration — automate payment workflows with TypeScript, Node.js, and Next.js

Install
npx -y skills add rexedge/interswitch --skill interswitch-setup

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Set up the Interswitch API client, environment variables, OAuth 2.0 authentication, and TypeScript helpers for server-side payment integration. Use this skill whenever starting a new Interswitch integration, configuring API keys, creating a reusable fetch wrapper, or setting up the foundation for any Interswitch feature. Also use when you see errors related to missing CLIENT_ID, SECRET_KEY, authentication failures, or need to understand Interswitch's base URLs, passport OAuth flow, response format, currencies, or environment switching (test/live).

SKILL.md

8.2 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it

Interswitch Setup

Set up the foundational Interswitch API client, OAuth 2.0 authentication, and environment configuration for TypeScript/JavaScript server-side applications.

API Fundamentals

PropertyValue
Test Passport URLhttps://passport.k8.isw.la/passport/oauth/token
Live Passport URLhttps://passport.interswitchng.com/passport/oauth/token
Test Collections URLhttps://qa.interswitchng.com
Live Collections URLhttps://interswitchng.com
Test Web Checkout URLhttps://newwebpay.qa.interswitchng.com
Live Web Checkout URLhttps://newwebpay.interswitchng.com
Auth MethodOAuth 2.0 Client Credentials
Content Typeapplication/json
Amount UnitMinor currency (kobo for NGN — multiply amount × 100)
Currency Code566 (NGN), 840 (USD)

Environment Variables

Create a .env file:

# Environment: 'test' or 'live'
INTERSWITCH_ENV=test

# General Integration Credentials
TEST_CLIENT_ID=IKIAB23A4E2756605C1ABC33CE3C287E27267F660D61
TEST_SECRET_KEY=secret
TEST_MERCHANT_CODE=MX6072

# Card Payment API Credentials (separate set)
CARD_API_CLIENT_ID=IKIA3B827951EA3EC2E193C51DA1D22988F055FD27DE
CARD_API_SECRET_KEY=ajkdpGiF6PHVrwK
CARD_API_MERCHANT_CODE=MX21696
CARD_API_PAY_ITEM_ID=4177785

# Live credentials (get from Quickteller Business dashboard)
LIVE_CLIENT_ID=your_live_client_id
LIVE_SECRET_KEY=your_live_secret_key
LIVE_MERCHANT_CODE=your_live_merchant_code

# Optional
PAY_ITEM_ID=9405967
DEFAULT_WALLET_PIN=1234

The CLIENT_ID and SECRET_KEY must NEVER appear in client-side code or public repositories. Get live credentials from Quickteller Business Dashboard.

OAuth 2.0 Authentication

Interswitch uses OAuth 2.0 Client Credentials flow. Encode clientId:secretKey as Base64 and POST to the passport endpoint:

interface InterswitchConfig {
  env: 'test' | 'live';
  clientId: string;
  secretKey: string;
  merchantCode: string;
  passportUrl: string;
  collectionsBaseUrl: string;
  walletBaseUrl: string;
}

function getConfig(): InterswitchConfig {
  const isLive = process.env.INTERSWITCH_ENV === 'live';
  return {
    env: isLive ? 'live' : 'test',
    clientId: isLive
      ? process.env.LIVE_CLIENT_ID!
      : process.env.TEST_CLIENT_ID!,
    secretKey: isLive
      ? process.env.LIVE_SECRET_KEY!
      : process.env.TEST_SECRET_KEY!,
    merchantCode: isLive
      ? process.env.LIVE_MERCHANT_CODE!
      : process.env.TEST_MERCHANT_CODE!,
    passportUrl: isLive
      ? 'https://passport.interswitchng.com/passport/oauth/token'
      : 'https://passport.k8.isw.la/passport/oauth/token',
    collectionsBaseUrl: isLive
      ? 'https://interswitchng.com'
      : 'https://qa.interswitchng.com',
    walletBaseUrl: isLive
      ? 'https://interswitchng.com'
      : 'https://qa.interswitchng.com',
  };
}

Generate Access Token

interface AccessTokenResponse {
  access_token: string;
  token_type: string;
  expires_in: number;
  scope: string;
  merchant_code: string;
  requestor_id: string;
  payable_id: string;
  jti: string;
}

async function generateAccessToken(): Promise<AccessTokenResponse> {
  const config = getConfig();
  const credentials = Buffer.from(
    `${config.clientId}:${config.secretKey}`
  ).toString('base64');

  const response = await fetch(
    `${config.passportUrl}?grant_type=client_credentials`,
    {
      method: 'POST',
      headers: {
        Authorization: `Basic ${credentials}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Auth failed: ${response.status} ${response.statusText}`);
  }

  return response.json();
}

Reusable Auth Headers

async function getAuthHeaders(): Promise<Record<string, string>> {
  const token = await generateAccessToken();
  return {
    Authorization: `Bearer ${token.access_token}`,
    'Content-Type': 'application/json',
  };
}

Collections Passport (Passport v2)

Some endpoints (wallet-pay, split settlement) use a separate passport-v2 token:

async function generateCollectionsAccessToken(): Promise<AccessTokenResponse> {
  const config = getConfig();
  const isLive = config.env === 'live';

  const splitPassportUrl = isLive
    ? 'https://passport.interswitchng.com/passport-v2/oauth/token'
    : 'https://passport.k8.isw.la/passport-v2/oauth/token';

  const credentials = Buffer.from(
    `${config.clientId}:${config.secretKey}`
  ).toString('base64');

  const response = await fetch(
    `${splitPassportUrl}?grant_type=client_credentials`,
    {
      method: 'POST',
      headers: {
        Authorization: `Basic ${credentials}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Collections auth failed: ${response.status}`);
  }

  return response.json();
}

Generic API Request Helper

async function interswitchRequest<T>(
  endpoint: string,
  options: RequestInit = {},
  useCollectionsAuth = false
): Promise<T> {
  const config = getConfig();
  const headers = useCollectionsAuth
    ? await getCollectionsAuthHeaders()
    : await getAuthHeaders();

  const url = endpoint.startsWith('http')
    ? endpoint
    : `${config.collectionsBaseUrl}${endpoint}`;

  const response = await fetch(url, {
    ...options,
    headers: { ...headers, ...options.headers },
  });

  if (!response.ok) {
    const errorBody = await response.text();
    throw new Error(`Interswitch API error ${response.status}: ${errorBody}`);
  }

  return response.json();
}

async function getCollectionsAuthHeaders(): Promise<Record<string, string>> {
  const token = await generateCollectionsAccessToken();
  return {
    Authorization: `Bearer ${token.access_token}`,
    'Content-Type': 'application/json',
  };
}

Token Caching

Cache tokens to avoid unnecessary passport calls:

let cachedToken: { token: AccessTokenResponse; expiresAt: number } | null = null;

async function getCachedAccessToken(): Promise<string> {
  const now = Date.now();
  if (cachedToken && cachedToken.expiresAt > now) {
    return cachedToken.token.access_token;
  }

  const token = await generateAccessToken();
  cachedToken = {
    token,
    expiresAt: now + token.expires_in * 1000 - 60000, // 1 min buffer
  };

  return token.access_token;
}

InterswitchAuth (Legacy)

Some legacy endpoints use InterswitchAuth headers instead of OAuth:

import crypto from 'crypto';

function getInterswitchAuthHeaders(
  httpMethod: string,
  resourceUrl: string,
  clientId: string,
  secretKey: string
): Record<string, string> {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();
  const signatureCipher = `${httpMethod}&${encodeURIComponent(resourceUrl)}&${timestamp}&${nonce}&${clientId}&${secretKey}`;
  const signature = crypto
    .createHash('sha512')
    .update(signatureCipher)
    .digest('base64');

  return {
    Authorization: `InterswitchAuth ${Buffer.from(clientId).toString('base64')}`,
    Timestamp: timestamp,
    Nonce: nonce,
    Signature: signature,
    SignatureMethod: 'SHA512',
    'Content-Type': 'application/json',
  };
}

Developer Resources

ResourceURL
API Documentationhttps://docs.interswitchgroup.com/docs/home
API Referencehttps://isw-api.readme.io/reference
Developer Consolehttps://developer.interswitchgroup.com/
Quickteller Businesshttps://business.quickteller.com
Developer Community (Slack)https://iswdevelopercommunity.slack.com

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.