Case study · 2026 · representative engagement
NetSuite × iSAMS & Famly: one finance picture across schools and nurseries.
Connecting iSAMS (schools) and Famly (nurseries) to NetSuite OneWorld across 40 subsidiaries — billing, payments, occupancy and funded free-hours flowing in via API, with consolidated receivables, revenue-leakage detection and term-based revenue recognition.
1The challenge
A UK independent education group ran each of its schools on iSAMS and each of its nurseries on Famly, while group finance consolidated in NetSuite OneWorld. The three systems didn't talk to each other, so finance lived in spreadsheets and manual exports. The pain showed up everywhere:
- Manual rekeying at scale — fee invoices, receipts and pupil records were exported from 40 systems and re-entered into NetSuite every month, slow and error-prone
- No consolidated view — there was no single debtor ledger, no group occupancy picture, and no way to compare projected revenue to budget without rebuilding spreadsheets each term
- Revenue leakage — pupils enrolled but never invoiced went unnoticed; nobody could see enrolment and billing side by side
- Funded-hours gaps — local-authority funded free-hours entitlement for the nurseries was reconciled by hand, if at all
- A ten-day close — because every figure depended on manual exports, the group month-end stretched across roughly ten working days
The group needed the operational data in iSAMS and Famly to land in NetSuite automatically, accurately, and per-subsidiary — without a fragile integration that quietly corrupts the ledger.
2The approach
A fully native integration — built entirely inside NetSuite with SuiteScript 2.1 and no third-party iPaaS. iSAMS and Famly are reached directly over their own REST and Batch APIs using N/https callouts and RESTlet webhook receivers; orchestration, mapping, error-handling and monitoring all live in NetSuite as custom records and Suitelets.
The design is layered so each tier has one job, and every hard problem in a finance integration is handled explicitly rather than hoped away.
1 · Connectivity tier (native SuiteScript)
- Paginated polling — scheduled Map/Reduce scripts page through the iSAMS REST/Batch and Famly REST endpoints with
N/https, cursoring through large result sets without hitting script timeouts. - Webhook receivers — RESTlets accept near-real-time push from the source systems, secured with Token-Based Authentication under a dedicated, least-privilege integration role.
- OAuth2 token manager — client credentials and refresh tokens live in
N/secret/ encrypted fields, with a keep-alive job refreshing tokens before expiry so no run fails on a stale token. - Rate-limit & back-off — outbound calls respect each API's throttling limits, with exponential back-off and jitter on 429 / 5xx responses.
2 · Staging & orchestration
- Staging records — every raw payload lands in a custom staging record and moves through pending → validated → posted → error, so nothing posts half-mapped and any record is replayable.
- Mapping engine — custom records resolve subsidiary, fee code → item / GL, chart of accounts, tax code and currency; an unmapped value fails loudly into the queue rather than posting to the wrong place.
- Idempotency & echo-loop guard — external IDs de-duplicate inbound records, and a last-sync hash stops an update bouncing back as a new change.
- Dead-letter queue — after a capped number of retries a record is parked for review and one-click reprocessing from the admin Suitelet.
- Governance management — Map/Reduce yielding and unit budgeting keep long runs inside NetSuite's governance limits, rescheduling rather than failing mid-batch.
3 · Posting, control & observability
- Per-subsidiary posting — customers, invoices, credit memos and payments are written to the correct one of 40 subsidiaries and consolidate natively in OneWorld.
- Revenue & tax — term fees feed Advanced Revenue Management for correct period recognition; SuiteTax handles the education VAT treatment.
- Admin Suitelet — a single console for token / connection status, mapping management and run / queue monitoring.
- Audit & reconciliation — every run is logged to a custom record with GoBD-conformant retention, and nightly reconciliation jobs compare source vs NetSuite counts and alert finance on any drift.
- Optional writeback — once a payment is applied in NetSuite, clearance status can be pushed back to iSAMS / Famly over
N/https.
3Record mappings
The integration is only as trustworthy as its mappings. Below are the core object and field mappings from each source system into NetSuite, the custom control records that drive them, and the per-object sync behaviour.
| iSAMS source | NetSuite target | Key / logic |
|---|---|---|
| School / division (School Manager) | Subsidiary | One per setting; resolved via the subsidiary map |
| Year group / form / house | Class / Department segment | Via the structure map |
| Academic year / term + dates | Accounting period + custbody_term | Drives the ARM recognition schedule |
| Pupil / Student (pupil id) | Customer : Job (sub-customer) | External ID ISAMS-{host}-{pupilId}; idempotent upsert; entry/leaving dates retained |
| Billing contact (financial resp.) | Customer (AR account) | Only financially-responsible contacts become AR customers |
| Contact (parental resp.) | Contact linked to customer / job | Non-paying; not an AR account |
| Split-billing % per bill payer | Multiple invoices / split lines | Mirrors iSAMS split billing across payers |
| Fee / charge code (TUIT · LUNCH · TRIP · UNIFORM · WRAP-CARE · REG-DEP) | Item (+ GL + tax) | Resolved via the fee-code map; an unmapped code fails loudly into the queue |
| Fee bill (header + lines) | Invoice | External ID ISAMS-INV-{host}-{billId}; bill number to memo |
| Charge line (code, qty, net) | Invoice line item | Item, GL & tax from the fee-code map |
| VAT treatment | SuiteTax code | Tuition VAT-exempt; extras at the correct rate |
| Deferred income (fees in advance) | ARM deferral + recognition | Recognised across the term; replaces manual monthly journals |
| Bursary · scholarship · sibling · staff | Discount item / negative line | By award type; reported separately, not netted into fees |
| Receipt / payment (DD · card · BACS) | Customer Payment | Applied to the originating invoice |
| Credit note | Credit Memo | Leaver credits / refunds |
| Famly source field | NetSuite target | Key / logic |
|---|---|---|
sites (siteId) | Subsidiary / Location | External ID FAMLY-{siteId}; via subsidiary map |
groups (rooms) | Class / Department | Per site |
children (childId) | Customer : Job (sub-customer) | External ID FAMLY-CHILD-{id}; sitesRelation.firstDay/lastDay → enrolment / leaver dates |
billPayers (billPayerId) | Customer (AR account) | NetSuite internal id written back to billPayers.externalId |
billPayers.children[].share.multiplier | Split ratio across jobs | Mirrors split billing |
billPayers.accountNumber / sortCode | Bank details (Direct Debit) | Masked in UI; access-controlled |
contacts (contactId) | Contact | Linked to job(s); parental / guardian / emergency |
invoices (invoiceId · invoiceNumber) | Invoice | External ID FAMLY-INV-{id} |
invoices.lines[].account.reference | Item + GL (via fee-code map) | Famly nominal code → NetSuite item / GL / tax |
invoices.lines[].childId · info · amount | Invoice line | Per-child line; BadMoney decimal parsed to currency |
payments (id) | Customer Payment | externalSystem = CUSTOM tag = echo-loop guard |
payments.invoices[].invoiceId | Application to NetSuite invoice | Matched on invoice external ID |
payments.viaFamlyPay · isDeposited · depositDate | Clearing / deposit reference | Bank reconciliation |
createCreditNote | Credit Memo | Refunds / leaver credits |
| Funded free-hours / LA funding | Funded item + LA-funding customer | See note below — reconciled expected → claimed → LA-paid → posted |
| Mechanism | How it works |
|---|---|
| Natural keys / external IDs | Every source record is stored as a NetSuite externalId (ISAMS-… / FAMLY-…) so re-processing is idempotent — upsert, never duplicate |
Payer cross-reference payer_xref | Resolves an iSAMS contact / Famly billPayerId to one NetSuite customer, then reuses it |
Echo-loop guard custbody_last_sync_hash | A content hash is compared before posting; unchanged payloads are skipped, so an update never ping-pongs back as a new change |
| Write-back to source | NetSuite id stamped to billPayers.externalId (Famly) and an agreed external-reference field (iSAMS); synced payments tagged externalSystem = CUSTOM |
Lineage custbody_source / _id | Each transaction carries its source system + id for figure → transaction → source drill-through |
| System | Credential | Storage & scope |
|---|---|---|
| iSAMS REST | API client credentials · per-key module permissions (Pupil Manager, Contacts, Fee Billing, School Manager) | In N/secret; least-privilege; server-side N/https over TLS |
| iSAMS Batch | API key + selected Batch Methods | Read-only; nightly reconciliation snapshot (XML) |
| Famly | X-Famly-Accesstoken — org or site token, expiring (7-day pre-expiry reminder) | In N/secret; minimum scopes; Public API preferred, Internal API isolated behind an adapter |
| NetSuite | Dedicated Token-Based-Auth integration role | Least-privilege; full sync-log audit trail; pupil / young-child data minimised under UK GDPR |
| Record | Purpose |
|---|---|
customrecord_subsidiary_map | Maps each iSAMS school / Famly setting to its NetSuite subsidiary |
customrecord_fee_code_map | Maps source fee / billing codes to NetSuite items and GL accounts |
customrecord_coa_map | Maps any source GL account to its NetSuite counterpart — unmapped fails loudly |
customrecord_tax_map | Maps source tax treatment to SuiteTax codes |
customrecord_currency_map | Currency code + exchange-rate handling for any non-GBP flows |
customrecord_sync_log | Per-run logging: counts, durations, errors, payload references |
custbody_last_sync_hash | Echo-loop protection — stops an update bouncing back as a new change |
customrecord_payer_xref | Cross-references each source payer (iSAMS contact / Famly billPayerId) to its NetSuite customer |
customrecord_dead_letter | Parks failed records with the error and a one-click re-queue action |
custbody_source / custbody_source_id | Stamps source system + id on each transaction for full lineage |
| Object | Direction | Trigger | Frequency |
|---|---|---|---|
| Customers | inbound | New / changed payer | Hourly |
| Invoices | inbound | Billing run | Hourly (REST) / nightly (Batch) |
| Payments | inbound | Receipt posted | Hourly |
| Occupancy snapshot | inbound | Scheduled | Daily |
| Payment / clearance status | writeback | Payment applied in NetSuite | Optional · hourly |
Funded free-hours & Local-Authority funding. The Famly Public API exposes invoices, lines and payments but not a discrete funded-hours object. We model the funded element as dedicated line types (e.g. FUNDED-15H / FUNDED-30H) mapped to a funded-hours item with the Local Authority as the funding customer, then reconcile expected → claimed → LA-paid → posted; where that data is not in the Public API it is read through an isolated Internal-API adapter.
Field and record names shown are representative of how SuiteHeroes structures this integration; exact identifiers are agreed per engagement.
4What it unlocks
One consolidated debtor ledger
A single AR view with aging and a collections worklist across all 40 entities — replacing 40 separate logins and a stack of spreadsheets.
Occupancy that feeds revenue
Pupil and booking counts from iSAMS and Famly drive projected revenue, so unfilled places become a number leadership can act on.
Revenue-leakage detection
Pupils enrolled but not yet invoiced are flagged automatically — fees that would otherwise quietly go uncollected.
Funded free-hours reconciliation
Local-authority funded entitlement for the nurseries is matched against claims, surfacing any unreconciled balance per setting.
Revenue recognised properly
Term-based tuition and nursery fees feed Advanced Revenue Management for correct period recognition, not a cash-in-cash-out posting.
A faster, calmer close
With rekeying gone, the group month-end moved from around ten days to roughly three, and finance manages by exception.
5Technologies used
"Schools and nurseries don't have a NetSuite problem — they have a join problem. The operational systems know who's enrolled and what's owed; NetSuite is the ledger. Put a disciplined staging layer between them and the whole group becomes visible in one place."