Building a POS Integration Without Controlling the POS
TIL that integrating with a system you don't own means designing for its changes, not just its current behavior — the architecture that survived was the one that assumed the POS would break the integration eventually.
The Problem #
The delivery system's entire order pipeline starts with data extracted from Recaho's POS via a content script. Early architecture treated that extraction as a reliable data source, the same way it would treat a database query — because most of my prior integration experience was with systems I did control.
Context #
There's no API access, no advance notice of changes, and no SLA with the POS vendor. Every assumption about "how the POS behaves" is really an assumption about how it behaved the last time I looked.
What I Tried #
Built the order pipeline (extension → API → database → dispatch dashboard) as a straight-through pipe: extract data, forward it immediately, trust it's correct.
What Went Wrong #
A POS update once changed how delivery times were formatted on the page. The extraction still "succeeded" — it returned a string, just the wrong one — and that malformed value flowed straight through to the dispatch dashboard and displayed a nonsense delivery time to a real dispatcher.
The Solution #
Added a validation boundary right at the extraction point: known-shape checks (does this look like a time, a valid order ID, a real address) before anything gets forwarded, with extraction failures logged and quarantined rather than passed downstream as if they were valid.
function validateExtractedOrder(raw) {
const timeOk = /^\d{1,2}:\d{2}\s?(AM|PM)$/i.test(raw.deliveryTime);
if (!timeOk) {
console.error("[extension] Unexpected delivery time format:", raw.deliveryTime);
return null; // quarantine, don't forward garbage downstream
}
return raw;
}Why It Works #
Treating the POS as a source of truth I could fully trust was the actual bug — it's a source of data, and any external, unowned source of data needs a validation boundary at the point it enters my system, the same way user input does. The rest of the pipeline stayed simple because the messiness gets caught at the edge, not propagated through it.
Lessons Learned #
Integrations without an API contract don't get to skip having a contract — they just have to define and enforce one themselves, at the boundary, since the other side isn't going to.
What I Would Do Differently #
I'd design the validation boundary before writing the first line of the pipeline that consumes extracted data, since "the POS changed its markup" was always a matter of when, not if.
Related Concepts #
Input validation at trust boundaries, defensive integration architecture, quarantine-and-log vs. fail-fast error handling.
Related content
Designing a Complete Delivery-Management System as a Solo Developer
TIL that a system spanning a browser extension, a React Native app, an API, and a database only stayed maintainable solo because I treated the API as the one real product and everything else as a thin client of it.
Connecting a Chrome Extension to a Node.js API
TIL that a fetch call from a content script that worked fine in the console failed silently from the actual extension, because content scripts run in an isolated world with their own CORS and CSP rules.
Designing One API for Both a Web Dashboard and a Mobile App
TIL that a REST API originally shaped around the web dashboard's screens needed real redesign, not just new endpoints, once a React Native app started consuming it too.