cd /news/developer-tools/building-an-automated-pos-settlement… · home topics developer-tools article
[ARTICLE · art-45201] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

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.

read7 min views1 publishedJun 30, 2026

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.

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.

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

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. 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 on Slack and share what you're building.

── more in #developer-tools 4 stories · sorted by recency
── more on @interswitch 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/building-an-automate…] indexed:0 read:7min 2026-06-30 ·