Building an Automated POS Settlement Monitor with Interswitch Transaction Search A developer built an automated POS settlement monitor using the Interswitch Transaction Search API. The tool authenticates via Interswitch Passport, searches each terminal's transactions for a given day, and pulls full settlement details to flag pending settlements. It focuses on POS Purchase and POS Transfer transactions, which have full transaction and settlement coverage. 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 https://docs.interswitchgroup.com/docs/transaction-search ; one that takes your terminals, finds the day's transactions, and tells you exactly what settled and what didn't. 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 https://interswitchgroup.com/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. 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. Get your credentials first: Create an account on the Interswitch Developer Console https://developer.interswitchgroup.com/ Set up a Project and select Transaction Search from the available APIs A test Client ID and Secret Key are generated for you 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; } 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 } 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; } 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? . 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. 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. 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 https://help.interswitchng.com . Build that into your notFound handling rather than letting it break the run. 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: Running POS terminals and building reconciliation tooling? Join our community https://join.slack.com/t/iswdevelopercommunity/shared invite/zt-3tef2a52s-0mI470Fh7hJ R8O9wfHHIw on Slack and share what you're building.