Reconcile Transactions
NextAPI's ledger records every peso movement as a posting — a timestamped, double-entry ledger entry. Use the postings API to reconcile your platform's records against NextAPI without manual bank statement matching.
This guide shows you how to pull postings, match them to your internal records, and identify discrepancies.
How the NextAPI ledger works
Every balance change on an account creates one or more postings:
- Collection received → credit posting on the account
- Payout sent → debit posting on the account
- Internal transfer → debit on source, credit on destination
Postings are grouped into transactions (atomic operations). A payout transaction might have a debit posting on the merchant account and a credit posting on a clearing account.
You never need to cross-reference bank statements — the NextAPI ledger is the authoritative record.
Step 1: Fetch account postings
- cURL
- Node.js
- Python
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
"https://api.partners.nextpay.world/v2/accounts/acct_01HXYZ/postings?limit=100"
async function getAccountPostings(accountId, options = {}) {
const params = new URLSearchParams({
limit: options.limit || 100,
...(options.after && { after: options.after }),
...(options.before && { before: options.before }),
});
const response = await fetch(
`https://api.partners.nextpay.world/v2/accounts/${accountId}/postings?${params}`,
{
headers: {
'Authorization': 'Basic ' + Buffer.from('YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').toString('base64'),
},
}
);
return response.json();
}
import requests
import base64
def get_account_postings(account_id, limit=100, after=None):
credentials = base64.b64encode(b'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET').decode()
params = {'limit': limit}
if after:
params['after'] = after
response = requests.get(
f'https://api.partners.nextpay.world/v2/accounts/{account_id}/postings',
headers={'Authorization': f'Basic {credentials}'},
params=params
)
response.raise_for_status()
return response.json()
Response
{
"data": [
{
"id": "post_01HABC",
"account_id": "acct_01HXYZ",
"transaction_id": "txn_01HDEF",
"direction": "credit",
"amount": 150000,
"running_balance": 150000,
"description": "Payment received - Order #98765",
"reference": "order-98765",
"created_at": "2025-11-15T10:35:22Z"
},
{
"id": "post_01HXYZ",
"account_id": "acct_01HXYZ",
"transaction_id": "txn_01HGHI",
"direction": "debit",
"amount": 100000,
"running_balance": 50000,
"description": "Payout to BPI ****7890",
"reference": "payout-nov-2025-EMP001",
"created_at": "2025-11-15T18:00:00Z"
}
],
"meta": {
"total": 247,
"has_more": true,
"next_cursor": "post_01HAAA"
}
}
Key fields:
direction—credit(money in) ordebit(money out)amount— always positive, in centavosrunning_balance— account balance after this postingreference— the value you set when creating the payout or payment intent
Step 2: Page through all postings
The API is paginated. Use cursor-based pagination to fetch all postings for a period:
async function getAllPostings(accountId, startDate, endDate) {
const postings = [];
let cursor = null;
do {
const params = new URLSearchParams({
limit: 100,
...(cursor && { after: cursor }),
...(startDate && { created_after: startDate }),
...(endDate && { created_before: endDate }),
});
const response = await fetch(
`https://api.partners.nextpay.world/v2/accounts/${accountId}/postings?${params}`,
{ headers: { 'Authorization': 'Basic ...' } }
);
const page = await response.json();
postings.push(...page.data);
cursor = page.meta.has_more ? page.meta.next_cursor : null;
} while (cursor);
return postings;
}
Step 3: Match against internal records
Compare NextAPI postings to your internal transaction records:
async function reconcileDay(accountId, date, internalRecords) {
const startOfDay = `${date}T00:00:00Z`;
const endOfDay = `${date}T23:59:59Z`;
const postings = await getAllPostings(accountId, startOfDay, endOfDay);
const discrepancies = [];
for (const posting of postings) {
// Skip internal platform postings without a user-facing reference
if (!posting.reference) continue;
const internalRecord = internalRecords.find(r => r.reference === posting.reference);
if (!internalRecord) {
discrepancies.push({
type: 'MISSING_INTERNAL_RECORD',
posting,
message: `Posting ${posting.id} (ref: ${posting.reference}) has no matching internal record`,
});
continue;
}
if (internalRecord.amount !== posting.amount) {
discrepancies.push({
type: 'AMOUNT_MISMATCH',
posting,
internalRecord,
message: `Amount mismatch: NextAPI ${posting.amount} vs internal ${internalRecord.amount}`,
});
}
}
// Check for internal records with no corresponding posting
for (const record of internalRecords) {
const posting = postings.find(p => p.reference === record.reference);
if (!posting) {
discrepancies.push({
type: 'MISSING_NEXTAPI_POSTING',
internalRecord: record,
message: `Internal record ${record.reference} has no corresponding NextAPI posting`,
});
}
}
return { postings, discrepancies };
}
Step 4: Verify balance integrity
Cross-check the running balance on the last posting against the current account balance:
async function verifyBalance(accountId) {
const [postingsRes, balanceRes] = await Promise.all([
fetch(`https://api.partners.nextpay.world/v2/accounts/${accountId}/postings?limit=1`, {
headers: { 'Authorization': 'Basic ...' }
}),
fetch(`https://api.partners.nextpay.world/v2/accounts/${accountId}/balances`, {
headers: { 'Authorization': 'Basic ...' }
}),
]);
const { data: postings } = await postingsRes.json();
const balance = await balanceRes.json();
const lastPosting = postings[0];
if (lastPosting && lastPosting.running_balance !== balance.available) {
console.warn(
`Balance discrepancy: last posting running_balance=${lastPosting.running_balance}, ` +
`current available=${balance.available}`
);
// This can happen if there are pending postings — check balance.pending
}
return balance;
}
The available balance excludes pending funds. If running_balance on the last posting doesn't match available, check balance.pending — funds in transit may not yet be settled.
Webhook-based reconciliation
For real-time reconciliation (rather than end-of-day batch), use webhooks. Each significant event fires a webhook with the relevant reference:
| Event | What it means |
|---|---|
payment_intent.paid | A collection landed — credit will appear in postings |
payout_request.completed | A payout settled — debit will appear in postings |
payout_request.failed | A payout failed — no debit posted |
Match webhook events to your internal records immediately. Use the ledger as your audit trail for any disputes.
Key fields for reconciliation
| Field | Description | Use for |
|---|---|---|
reference | Your reference string | Primary match key to internal records |
transaction_id | NextAPI's transaction ID | Grouping related postings |
direction | credit or debit | Categorizing money in vs money out |
amount | Amount in centavos | Amount verification |
running_balance | Balance after this posting | Integrity checks |
created_at | Timestamp | Date-range filtering |
Related
- Wallet Structure — how accounts and balances are organized
- Payout Lifecycle — when postings are created for payouts
- Collections Lifecycle — when postings are created for collections
- Setup Webhooks — real-time event notifications