If you run a business with POS terminals - a retail chain, a string of agent locations, a payments business with terminals in the field, you know the evening ritual. Transactions came in all day. Now you need to know which ones actually settled, which are still pending, and whether the money that hit your account matches what your terminals reported.
This guide does that. We'll build an automated settlement monitor using the Interswitch Transaction Search API; one that takes your terminals, finds the day's transactions, and tells you exactly what settled and what didn't.
On Coverage
Before we write a line of code, you need to know what Transaction Search actually covers, because building on the wrong assumption wastes your time.
Per Interswitch's transaction coverage, here's what's fully supported today:
| Transaction Type | Transaction | Settlement |
|---|---|---|
| Purchase (POS & Web) | Full | Full |
| Transfer (ATM & POS) | Full | Full |
| Cash Withdrawal | Full | Full |
| Agency Banking | Full | Partial |
| Quickteller Transfers | Partial | Partial |
The two types we'll build on - POS Purchase and POS Transfer - have full transaction and settlement coverage. That's deliberate. It means every reconciliation lookup in this guide returns complete data.
One thing to note: Interswitch Payment Gateway (IPG) purchases are not currently covered by Transaction Search. If you're verifying IPG web checkout payments, that's a different flow. Transaction Search is built for transactions that flow through the switch: POS, ATM, cash withdrawal, transfers.
What We're Building
A settlement monitor that does three things:
Authenticates against Interswitch Passport for an access token
Searches each terminal's transactions for a given day
Pulls full settlement details for each transaction and flags anything not yet settled
By the end you'll have a function you can drop into a cron job that runs every evening and emails you a settlement report.
Step 1 - Setup Your Project
Get your credentials first:
Create an account on the Interswitch Developer Console
Set up a Project and select Transaction Search from the available APIs
A test Client ID and Secret Key are generated for you
Step 2 - Authentication
Every Transaction Search call needs a Bearer token, obtained by exchanging your Client ID and Secret against the Interswitch Passport endpoint. Tokens are short-lived - cache and refresh rather than requesting one per call.
javascript
// auth.js
async function getAccessToken(clientId, secret) {
const credentials = Buffer.from(`${clientId}:${secret}`).toString('base64');
const response = await fetch('https://passport-sandbox.interswitchng.com/passport/oauth/token ', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'grant_type=client_credentials&scope=profile'
});
const data = await response.json();
return data.access_token;
}
Step 3 - Find A Terminal's Transaction
This is where the POS case study comes alive. Quick Search lets you find transactions by terminal_id, exactly the field you have for every one of your POS devices. Combine it with a date and you get every transaction that terminal processed that day.
| Field | Description | Notes |
|---|---|---|
| terminal_id | Identifies a terminal belonging to a merchant | 8 chars — you have this for every device |
| merchant_code | Uniquely identifies a registered merchant | 12 chars |
| stan | Transaction number from the terminal | 6 digits |
| rrn | Retrieval Reference Number | 12 digits |
| masked_pan | Card number (first 6 + last 4 visible) | 16–19 digits |
| start_date | Transaction date (YYYY-MM-DD) | Required |
| transaction_amount | Amount in lower denomination (₦20 → 2000) |
Two rules that catch everyone: start_date is compulsory, and transaction_amount is in the lower denomination, ₦200 is 20000, not 200.
javascript
// findTerminalTransactions.js
async function searchByTerminal(token, clientId, terminalId, date) {
const response = await fetch(
'https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/quick-search?page_size=20&page_number=1',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'ClientId': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
terminal_id: terminalId,
start_date: date // required
})
}
);
return await response.json();
}
A successful search returns a 202 with a data array. Each item carries the field that matters most — the transaction_id:
json
{
"responseMessage": "Transactions Received Successfully",
"responseCode": "202",
"dataSize": 1,
"data": [
{
"retrieval_reference_number": "696843517287",
"merchant_code": "2057LA200002957",
"masked_pan": "519911******3279",
"terminal_id": "20573ZLY",
"stan": "373758",
"transaction_date": "2023-10-09",
"transaction_amount": 210000,
"transaction_id": "958804c0-77fb-11ee-a39f-f7013f7f10c0",
"acquirer_code": "ZIB",
"issuer_code": "GTB"
}
],
"errors": null
}
Handling a full day of transactions
A busy terminal does more than 20 transactions a day, and search caps at 20 per page (page_size can't exceed 20 - try and you get a 400). Use the cursor to page through. Leave it blank on the first call; the response gives you a cursor and a hasMorePages flag. Keep passing the cursor until hasMorePages is false.
javascript
async function getAllTerminalTransactions(token, clientId, terminalId, date) {
let all = [];
let cursor = "";
let hasMore = true;
while (hasMore) {
const res = await fetch(
'https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/quick-search?page_size=20&page_number=1',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'ClientId': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({ terminal_id: terminalId, start_date: date, cursor })
}
);
const page = await res.json();
if (page.data) all = all.concat(page.data);
cursor = page.cursor;
hasMore = page.hasMorePages;
}
return all;
}
Step 4 - Pull Settlement Details
Search tells you a transaction happened. To know whether the money settled, take the transaction_id and call Get Transaction Details.
javascript
// getDetails.js
async function getTransactionDetails(token, clientId, transactionId) {
const response = await fetch(
`https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/transaction?transaction_id=${transactionId}`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'client_id': clientId,
'Content-Type': 'application/json'
}
}
);
return await response.json();
}
The response breaks into four blocks. For settlement reconciliation, the first two are what you need:
json
{
"data": [{
"globalOutputData": {
"category": "PURCHASE",
"amount": "210000",
"transactionDate": "2023-10-09T17:47:35.240",
"transactionStatus": "COMPLETED",
"settlementDate": "2023-10-10",
"settlementStatus": "SETTLED"
},
"transactionData": {
"terminalId": "20573ZLY",
"stan": "373758",
"channel": "POS",
"rrn": "696843517287",
"responseCode": "00",
"responseMessage": "Approved or completed successfully",
"merchantCode": "2057LA200002957"
},
"settlementData": {
"settlementBreakdownList": [ /* fee breakdown by party */ ],
"settlementDataAvailable": true
}
}]
}
The globalOutputData block answers the two questions your reconciliation is really asking: transaction status (did the payment complete?) and settlement status (did the money move?).
The Insight That Saves You: Transaction Status and Settlement Status Are Different
This is the single most important thing in this guide.
A POS transaction can be COMPLETED but PENDING SETTLEMENT. The customer's card was charged, the purchase succeeded, your terminal printed "approved" but the money has not yet landed in your settlement account.
These are two independent facts.
The most common reconciliation mistake is treating "transaction succeeded" as "money received." They are not the same, and the gap between them is exactly what you're monitoring for. A transaction stuck in PENDING SETTLEMENT for longer than expected is the thing you want flagged.
The statuses you'll see:PENDING, COMPLETED, REVERSED Settlement status: SETTLED, PENDING SETTLEMENT, NO SETTLEMENT, UNSUPPORTED
And within the transaction data, the response code maps cleanly: 00 is approved, 09 (or none) is pending, anything else (91, 06, 51...) is a failure.
Putting It Together: The Settlement Monitor
Here's the whole thing: give it your terminals and a date, get back a clean settlement report flagging anything that hasn't settled:
javascript
async function runSettlementMonitor(clientId, secret, terminalIds, date) {
const token = await getAccessToken(clientId, secret);
const report = { settled: [], pending: [], failed: [], notFound: [] };
for (const terminalId of terminalIds) {
const transactions = await getAllTerminalTransactions(token, clientId, terminalId, date);
if (!transactions.length) {
report.notFound.push(terminalId);
continue;
}
for (const txn of transactions) {
const details = await getTransactionDetails(token, clientId, txn.transaction_id);
const record = details.data[0].globalOutputData;
const entry = {
terminalId,
amount: record.amount,
transactionStatus: record.transactionStatus,
settlementStatus: record.settlementStatus,
settlementDate: record.settlementDate
};
if (record.settlementStatus === "SETTLED") {
report.settled.push(entry);
} else if (record.transactionStatus === "COMPLETED") {
report.pending.push(entry); // completed but not yet settled — watch these
} else {
report.failed.push(entry);
}
}
}
return report;
}
// Run it every evening for all your terminals
const report = await runSettlementMonitor(
clientId, secret,
["20573ZLY", "20577C9O", "2TEP5C7W"],
"2023-10-09"
);
console.log(`Settled: ${report.settled.length}`);
console.log(`Pending settlement (watch these): ${report.pending.length}`);
console.log(`Failed: ${report.failed.length}`);
Drop that into a cron job, pipe the output into an email or a Slack webhook, and you've automated reconciliation. The pending bucket is one to watch, those are completed transactions whose money hasn't landed yet.
When A Transaction Isn't Found
Interswitch is explicit that Transaction Search hasn't reached 100% coverage. If a POS transaction you know exists doesn't come back, treat the 404 as "not found in search" rather than "doesn't exist," and reconfirm through the Interswitch Help Desk. Build that into your notFound handling rather than letting it break the run.
Wrapping Up
We took three API calls: authenticate, search by terminal, get settlement details, and turned them into an automated settlement monitor for a POS business.
The key isn't any single call. It's the insight that transaction status and settlement status are different questions, and that the gap between them is exactly what reconciliation is about.
Resources:
- Getting Started
- Transaction Search Overview
- Transaction Set Coverage
- Quick Search Reference
- Get Transaction Details
- Response Codes & Statuses
Running POS terminals and building reconciliation tooling? Join our community on Slack and share what you're building.
Top comments (0)