{"slug": "building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search", "title": "Building an Automated POS Settlement Monitor with Interswitch Transaction Search", "summary": "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.", "body_md": "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.\n\nThis 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.\n\nBefore we write a line of code, you need to know what Transaction Search actually covers, because building on the wrong assumption wastes your time.\n\nPer Interswitch's transaction coverage, here's what's fully supported today:\n\n| Transaction Type | Transaction | Settlement |\n|---|---|---|\n| Purchase (POS & Web) | Full | Full |\n| Transfer (ATM & POS) | Full | Full |\n| Cash Withdrawal | Full | Full |\n| Agency Banking | Full | Partial |\n| Quickteller Transfers | Partial | Partial |\n\nThe 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.\n\nOne 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.\n\nA settlement monitor that does three things:\n\n**Authenticates** against Interswitch Passport for an access token\n\n**Searches** each terminal's transactions for a given day\n\n**Pulls** full settlement details for each transaction and flags anything not yet settled\n\nBy the end you'll have a function you can drop into a cron job that runs every evening and emails you a settlement report.\n\nGet your credentials first:\n\nCreate an account on the [Interswitch Developer Console](https://developer.interswitchgroup.com/)\n\nSet up a Project and select Transaction Search from the available APIs\n\nA test Client ID and Secret Key are generated for you\n\nEvery 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.\n\n```\njavascript\n// auth.js\nasync function getAccessToken(clientId, secret) {\n    const credentials = Buffer.from(`${clientId}:${secret}`).toString('base64');\n\n    const response = await fetch('https://passport-sandbox.interswitchng.com/passport/oauth/token ', {\n        method: 'POST',\n        headers: {\n            'Authorization': `Basic ${credentials}`,\n            'Content-Type': 'application/x-www-form-urlencoded'\n        },\n        body: 'grant_type=client_credentials&scope=profile'\n    });\n\n    const data = await response.json();\n    return data.access_token;\n}\n```\n\nThis 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.\n\n| Field | Description | Notes |\n|---|---|---|\n| terminal_id | Identifies a terminal belonging to a merchant | 8 chars — you have this for every device |\n| merchant_code | Uniquely identifies a registered merchant | 12 chars |\n| stan | Transaction number from the terminal | 6 digits |\n| rrn | Retrieval Reference Number | 12 digits |\n| masked_pan | Card number (first 6 + last 4 visible) | 16–19 digits |\n| start_date | Transaction date (YYYY-MM-DD) | Required |\n| transaction_amount | Amount in lower denomination (₦20 → 2000) |\n\nTwo rules that catch everyone: start_date is compulsory, and transaction_amount is in the lower denomination, ₦200 is 20000, not 200.\n\n```\njavascript\n// findTerminalTransactions.js\nasync function searchByTerminal(token, clientId, terminalId, date) {\n    const response = await fetch(\n        'https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/quick-search?page_size=20&page_number=1',\n        {\n            method: 'POST',\n            headers: {\n                'Authorization': `Bearer ${token}`,\n                'ClientId': clientId,\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify({\n                terminal_id: terminalId,\n                start_date: date          // required\n            })\n        }\n    );\n\n    return await response.json();\n}\n```\n\nA successful search returns a 202 with a data array. Each item carries the field that matters most — the transaction_id:\n\n```\njson\n{\n    \"responseMessage\": \"Transactions Received Successfully\",\n    \"responseCode\": \"202\",\n    \"dataSize\": 1,\n    \"data\": [\n        {\n            \"retrieval_reference_number\": \"696843517287\",\n            \"merchant_code\": \"2057LA200002957\",\n            \"masked_pan\": \"519911****** 3279\",\n            \"terminal_id\": \"20573ZLY\",\n            \"stan\": \"373758\",\n            \"transaction_date\": \"2023-10-09\",\n            \"transaction_amount\": 210000,\n            \"transaction_id\": \"958804c0-77fb-11ee-a39f-f7013f7f10c0\",\n            \"acquirer_code\": \"ZIB\",\n            \"issuer_code\": \"GTB\"\n        }\n    ],\n    \"errors\": null\n}\n```\n\nA 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.\n\n```\njavascript\nasync function getAllTerminalTransactions(token, clientId, terminalId, date) {\n    let all = [];\n    let cursor = \"\";\n    let hasMore = true;\n\n    while (hasMore) {\n        const res = await fetch(\n            'https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/quick-search?page_size=20&page_number=1',\n            {\n                method: 'POST',\n                headers: {\n                    'Authorization': `Bearer ${token}`,\n                    'ClientId': clientId,\n                    'Content-Type': 'application/json'\n                },\n                body: JSON.stringify({ terminal_id: terminalId, start_date: date, cursor })\n            }\n        );\n        const page = await res.json();\n        if (page.data) all = all.concat(page.data);\n        cursor = page.cursor;\n        hasMore = page.hasMorePages;\n    }\n\n    return all;\n}\n```\n\nSearch tells you a transaction happened. To know whether the money settled, take the transaction_id and call Get Transaction Details.\n\n```\njavascript\n// getDetails.js\nasync function getTransactionDetails(token, clientId, transactionId) {\n    const response = await fetch(\n        `https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/transaction?transaction_id=${transactionId}`,\n        {\n            method: 'POST',\n            headers: {\n                'Authorization': `Bearer ${token}`,\n                'client_id': clientId,\n                'Content-Type': 'application/json'\n            }\n        }\n    );\n    return await response.json();\n}\n```\n\nThe response breaks into four blocks. For settlement reconciliation, the first two are what you need:\n\n```\njson\n{\n    \"data\": [{\n        \"globalOutputData\": {\n            \"category\": \"PURCHASE\",\n            \"amount\": \"210000\",\n            \"transactionDate\": \"2023-10-09T17:47:35.240\",\n            \"transactionStatus\": \"COMPLETED\",\n            \"settlementDate\": \"2023-10-10\",\n            \"settlementStatus\": \"SETTLED\"\n        },\n        \"transactionData\": {\n            \"terminalId\": \"20573ZLY\",\n            \"stan\": \"373758\",\n            \"channel\": \"POS\",\n            \"rrn\": \"696843517287\",\n            \"responseCode\": \"00\",\n            \"responseMessage\": \"Approved or completed successfully\",\n            \"merchantCode\": \"2057LA200002957\"\n        },\n        \"settlementData\": {\n            \"settlementBreakdownList\": [ /* fee breakdown by party */ ],\n            \"settlementDataAvailable\": true\n        }\n    }]\n}\n```\n\nThe globalOutputData block answers the two questions your reconciliation is really asking: transaction status (did the payment complete?) and settlement status (did the money move?).\n\nThis is the single most important thing in this guide.\n\nA 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.\n\nThese are two independent facts.\n\nThe 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.\n\nThe statuses you'll see:*PENDING, COMPLETED, REVERSED Settlement status: SETTLED, PENDING SETTLEMENT, NO SETTLEMENT, UNSUPPORTED*\n\nAnd 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.\n\nHere's the whole thing: give it your terminals and a date, get back a clean settlement report flagging anything that hasn't settled:\n\n```\njavascript\nasync function runSettlementMonitor(clientId, secret, terminalIds, date) {\n    const token = await getAccessToken(clientId, secret);\n    const report = { settled: [], pending: [], failed: [], notFound: [] };\n\n    for (const terminalId of terminalIds) {\n        const transactions = await getAllTerminalTransactions(token, clientId, terminalId, date);\n\n        if (!transactions.length) {\n            report.notFound.push(terminalId);\n            continue;\n        }\n\n        for (const txn of transactions) {\n            const details = await getTransactionDetails(token, clientId, txn.transaction_id);\n            const record = details.data[0].globalOutputData;\n\n            const entry = {\n                terminalId,\n                amount: record.amount,\n                transactionStatus: record.transactionStatus,\n                settlementStatus: record.settlementStatus,\n                settlementDate: record.settlementDate\n            };\n\n            if (record.settlementStatus === \"SETTLED\") {\n                report.settled.push(entry);\n            } else if (record.transactionStatus === \"COMPLETED\") {\n                report.pending.push(entry);   // completed but not yet settled — watch these\n            } else {\n                report.failed.push(entry);\n            }\n        }\n    }\n\n    return report;\n}\n\n// Run it every evening for all your terminals\nconst report = await runSettlementMonitor(\n    clientId, secret,\n    [\"20573ZLY\", \"20577C9O\", \"2TEP5C7W\"],\n    \"2023-10-09\"\n);\n\nconsole.log(`Settled: ${report.settled.length}`);\nconsole.log(`Pending settlement (watch these): ${report.pending.length}`);\nconsole.log(`Failed: ${report.failed.length}`);\n```\n\nDrop 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.\n\nInterswitch 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.\n\nWe took three API calls: ** authenticate, search by terminal, get settlement details**, and turned them into an automated settlement monitor for a POS business.\n\nThe 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.\n\n**Resources:**\n\nRunning 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.", "url": "https://wpnews.pro/news/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search", "canonical_source": "https://dev.to/interswitchdevcommunity/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search-2c4k", "published_at": "2026-06-30 17:04:50+00:00", "updated_at": "2026-06-30 17:19:01.686125+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence"], "entities": ["Interswitch", "Interswitch Transaction Search API", "Interswitch Passport", "Interswitch Developer Console", "Interswitch Payment Gateway (IPG)"], "alternates": {"html": "https://wpnews.pro/news/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search", "markdown": "https://wpnews.pro/news/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search.md", "text": "https://wpnews.pro/news/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search.txt", "jsonld": "https://wpnews.pro/news/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search.jsonld"}}