Real-time sync from your own NetSuite scripts
Shipped
Turbobase keeps your Postgres mirror current in two ways: a real-time push from Oracle NetSuite the moment a record is saved, and a periodic sweep every four hours that catches anything the push missed. The push is what makes your data feel live; the sweep is a safety net, not the plan. The sweep reads what changed in bulk through SuiteQL rather than one record at a time, so it finishes in minutes on a busy account, and if a sweep or a first load is interrupted it saves its place and resumes automatically within a few minutes.
This page is for NetSuite developers with their own SuiteScripts in the account. It covers what the push already handles (more than most people expect), the cases it genuinely cannot see, and the one-line call that closes them.
What is already covered
Section titled “What is already covered”When your Turbobase bundle is installed, it deploys a User Event script for every record type you sync. Those deployments are released for every NetSuite execution context, so a save reaches Turbobase in real time no matter what performed it:
| How the record was saved | Real-time push? |
|---|---|
| A user in the NetSuite UI | Yes |
Your Suitelet calling record.save() |
Yes |
| A scheduled or Map/Reduce script | Yes |
| A workflow action | Yes |
| A RESTlet or SOAP/REST web services call | Yes |
record.submitFields() / inline edit |
Yes |
| CSV import with “Run Server SuiteScript and Trigger Workflows” | Yes |
What is not covered
Section titled “What is not covered”Five cases. The first four are NetSuite not telling Turbobase anything; the fifth is a push that did arrive, carrying a value NetSuite had not written yet.
-
CSV import with server scripts switched off. The import assistant’s “Run Server SuiteScript and Trigger Workflows” option is per-import. With it off, no User Event runs for any of the imported rows.
-
Parent records NetSuite recalculates behind the scenes. Creating an item fulfillment moves its sales order to Pending Billing; creating a vendor bill moves its purchase order to Fully Billed. NetSuite rewrites the parent without saving it, so no User Event fires on the parent.
Turbobase handles the common ones for you — eight standard pairs, found through the child’s Created From link — so for those the parent is re-read seconds after the child and there is nothing to do. It is still worth knowing why the parent’s own script never ran.
Outside those pairs, ask us to mark the relation. Your own record types are never one of the eight, and a link that is not Created From — a custom body or custom record field pointing at the record NetSuite rewrites — is not the one they follow. Tell support which record makes NetSuite recalculate which, and we mark that relation on your instance; from then on the recalculated record is re-read seconds after the one that moved it, instead of waiting for the next four-hourly refresh. It is a setting on our side, not a script change on yours.
-
Record types you have not enabled. Only the record types listed on your Turbobase Configuration record push at all.
-
Anything outside NetSuite — a middleware job or partner system that writes through an integration you control.
-
A document number that arrives as “To Be Generated”. This one is not a missing push — the push arrived within seconds, and the number was still the placeholder. NetSuite assigns the real number when it commits the record, which is after the point it tells us about the save, so what we are handed is the form’s placeholder.
Your tenant now asks NetSuite for the committed value before it stores anything, so the real number arrives on the first message. That covers the document number on a transaction, the separate transaction number on a check, the identifier NetSuite assigns a new customer, vendor, contact, partner, project or employee, and NetSuite’s own last-modified stamp.
This needs bundle TB 2.13.0 or later in your NetSuite account. On an earlier bundle the placeholder stands until the next four-hourly refresh; there is nothing to change in your own scripts either way, and a record that was already stored with a placeholder is corrected the next time it changes or the refresh re-reads it. Check your bundle version under Customization → SuiteBundler → Search & Install Bundles → List, and ask support to upgrade it.
A value NetSuite computes rather than stores — a running total, a balance — is not on the record at save time at all, so no read can reach it and it still waits for the refresh.
Your instance names the record types this is happening to. Press How to fix on Late arrivals and, where the realtime path is plainly delivering but certain records keep coming in on the scheduled catch-up, the panel lists exactly which record types — so you can go to the scripts that save those and nothing else, rather than auditing every record type you have. Clicking one of the late rows shows which fields the catch-up moved, which tells a real edit apart from a change nothing on screen would show.
The notify call
Section titled “The notify call”For cases 1 and 4, tell Turbobase directly. Add the module to your script’s
define list and call it after your write:
define(['N/record', '/SuiteScripts/tb/lib/tb_notify'], function (record, tb) { 'use strict';
function onRequest(context) { var soId = record.submitFields({ type: 'salesorder', id: context.request.parameters.id, values: { custbody_shipment_status: 'SHIPPED' } });
tb.notify('salesorder', soId); // ← the whole integration }
return { onRequest: onRequest };});If you already have the record loaded, pass it and skip the reload:
var rec = record.load({ type: 'salesorder', id: soId });// ... your changes, then rec.save() ...tb.notify('salesorder', soId, { record: rec });tb.notify(recordType, id, options)
| Argument | Meaning |
|---|---|
recordType |
NetSuite record type id — 'salesorder', 'inventoryitem', 'customrecord_my_type'. Use the SuiteScript spelling, the same string record.type returns. |
id |
Internal id of the record. |
options.record |
Optional loaded record, to avoid a reload. |
Returns true when an update was sent, false when it was skipped. It never
throws.
What it guarantees
Section titled “What it guarantees”- It never breaks your script. Delivery is fire-and-forget and every failure path is caught and logged. Your save cannot fail because Turbobase was unreachable.
- It never blocks. No waiting on a round trip.
- It respects your configuration. A record type you have switched off stays off — this call cannot re-enable it.
- It cannot loop. Records Turbobase itself wrote are skipped, and a notify that duplicates a User Event on the same save collapses into a single update.
About 20 governance units, or 30 if Turbobase has to load the record for you — against the 1,000 a Suitelet gets, or the 200 a User Event gets.
Calling it from somewhere that can’t import a module
Section titled “Calling it from somewhere that can’t import a module”Client scripts and workflow action scripts can use the TB Notify Suitelet instead — anything running inside NetSuite with the user’s session:
require(['N/https', 'N/url'], function (https, url) { https.post.promise({ url: url.resolveScript({ scriptId: 'customscript_tb_io_notify', deploymentId: 'customdeploy_tb_io_notify' }), body: JSON.stringify({ recordType: 'salesorder', id: soId }), headers: { 'Content-Type': 'application/json' } });});It replies { "ok": true, "sent": true }, or { "ok": true, "sent": false, "reason": "skipped" } when the record type is not enabled or the change was
Turbobase’s own write.
The Suitelet requires a NetSuite session and runs under the caller’s role. It is not available without login, so it is not reachable from outside NetSuite — token-based (TBA) credentials authenticate RESTlets, not Suitelets. For an external system, have it call your own integration and use the library from a server-side script there, or ask us for a RESTlet endpoint.
Seeing which records are missing the push
Section titled “Seeing which records are missing the push”Your instance’s Late arrivals tab (/data/sync/late-arrivals) lists the records the four-hourly sweep delivered first — so no real-time push arrived for them. That is the list of records hitting one of the four cases above, by record type and by whether the change was a create, an edit or a deletion, with each record type’s real-time rate beside the count. Start there rather than guessing which of your scripts needs the notify call: a record type sitting well below 100% is where it is worth adding.
A record the push already delivered is re-read by the sweep, found unchanged, and does not appear. See Source health for the full description of the tab.
Checking it worked
Section titled “Checking it worked”Your instance’s Source health page shows inbound webhook volume and any
User Event errors NetSuite logged. If a notify call is not landing, turn on
Debug mode there: NetSuite then logs every trigger and the reason for any
skip (not enabled, echo, no synced-field change) to the script execution
log, so you can see which gate a save is hitting.