What four marketplaces taught us about syncing from NetSuite
The code that sends a price to a marketplace is short. Everything around it — who can authorize the app, what zero means, what happens at 3 a.m. when a quota runs out, and whether a person can tell a preview from a send — is where the time went.
The why is in selling it at the price you meant. This is the how: a NetSuite project with a Suitelet console, a channel record per marketplace (the familiar ones — Amazon Seller Central, eBay, Walmart Marketplace, Etsy), and a scheduled heartbeat that pushes changes. What follows are the things that cost us time, in roughly the order they bit.
1. The API is the easy part. Authorization is not.
Every marketplace wants an app registered and then authorized by the seller account. That assumes someone can sign in as the account’s owner. On one marketplace, nobody could: the account had been opened by a company the client later acquired, the primary login’s two-step device was long gone, and day-to-day users — even with full permissions — couldn’t authorize an app. Workarounds through other consent flows failed with errors that pointed nowhere useful.
The resolution wasn’t technical. It was a documented ownership change with the marketplace: proof of the acquisition, a recent bank statement matching the business on file, then a reset of the owner’s two-step verification. It took real calendar time, and it’s worth checking before anyone writes a line of code.
2. Credentials go in exactly the field that holds them
Each marketplace issues tokens differently — long-lived refresh tokens, refresh tokens that rotate on every use, certificate IDs, client secrets with expiry dates. One channel failed its first discovery because a rotating refresh token had been saved in the field for a static one. The fix was moving a string, but the lesson is design: give each credential type its own clearly labelled field, store them as secrets, and record the expiry date of anything that expires, so the rotation is on a calendar and not a surprise.
3. Zero is not a quantity everywhere
You’d expect “set quantity to 0” to work on any marketplace. It doesn’t. One accepts it, another rejects a listing update of zero outright, and wants the listing deactivated instead — then reactivated once stock comes back. A sync that only knows how to send numbers will either fail every time stock runs out or, worse, leave the old positive quantity live.
function quantityAction(channel, listing, qty) {
if (qty > 0) {
return listing.active ? { op: 'setQty', qty }
: { op: 'reactivate', qty };
}
return channel.acceptsZero ? { op: 'setQty', qty: 0 }
: { op: 'deactivate' };
}
The same marketplace also required fields on every update that the documentation treated as optional. The first overnight run failed on every listing for that reason alone.
4. Rate limits need a circuit breaker, not a retry loop
The first night the scheduler ran on its own, one channel failed on every cycle, then hit its daily request limit, and every later cycle failed again and emailed about it. By morning the alert inbox was a wall of the same message.
Three fixes, all now standard in anything we build that calls an outside API on a schedule:
- Back off per listing. A listing that fails three cycles running is parked and reported, not retried forever.
- Trip a breaker per channel. A rate-limit response stops all calls to that channel until its quota window resets, instead of spending the next window’s calls on retries.
- Summarize, don’t stream. Alerts go out as a digest twice a day. One email with forty failures gets read. Forty emails get filtered.
Read-side calls need the same care. Scanning a large catalog one listing at a time can burn a search quota before the scan finishes, so scans are paced, and where a channel offers a bulk report, the sync requests the report instead.
5. A preview must never look like a send
The console shows, for each listing, what NetSuite says the price and quantity should be. Early versions showed that preview in the same columns as what had actually been sent. A user pushed one item, saw the wrong price in the row, and reasonably concluded the push had failed. It hadn’t; the row was showing a calculation, not a read-back.
The fix was structural, not cosmetic: three distinct values with three distinct labels — NetSuite wants, we sent, and marketplace shows (as of last read) — and a confirmation message that names exactly what was read back from the marketplace, not just “success.”
6. Only what is visibly selected is ever sent
A user ticked rows, filtered the grid, then pressed Send. The rows they had ticked and then filtered out of view went too. Technically consistent; practically a trap. The rule now is simple: if you can’t see it selected, it isn’t sent.
const toSend = rows.filter(r => r.selected && r.visible);
7. Name buttons for what they do
An early console had “Re-read marketplaces” and “Refresh from marketplace,” “Confirm” and “Verify,” “Send” and “Approve and send.” Each pair did something slightly different, and the person using it couldn’t tell which was which. We cut them to one button per job, renamed them for the outcome (Refresh everything, Check on marketplace), and replaced a confusing filter toggle with a tab that lists only the rows that have changes to send. A console is only finished when the person who runs it every day stops asking what a button does.
8. Keep the existing quantity rule
Midway through, a component-based kit calculation looked more accurate than the buildable-quantity field the business already used. It was withdrawn. The existing field was the one the old connector had used, the one the planners trusted, and the one the rest of the operation reacted to. Changing what “available” means in the same release as changing how it’s delivered makes every discrepancy impossible to diagnose. Change one thing at a time.
The short version
- Confirm account ownership and two-step access before you build.
- One field per credential type, with expiry dates written down.
- Learn what zero means on each channel.
- Back-off, circuit breakers and digests for anything scheduled.
- Never let a preview look like a send, and never send what isn’t visibly selected.
- Change the delivery or the definition, not both at once.
Related: selling it at the price you meant, and is the error you’re chasing the cause, or just a symptom?