Restaurants & retailShipped2025

Eshop

A storefront that agrees with the till.

A POS-connected online store: delta sync against OneHubPOS, two payment gateways, magic-link SSO, multi-tenant access control, and a dual-table design so a merchant's own customisations survive the next sync from the point of sale.

4 locations
one system, one catalogue
80% fewer
POS API calls after delta sync
2 gateways
NMI and Dejavoo

The problem

The POS owns the catalogue, and the merchant wants to change it

Menus, prices, taxes and modifiers all live in the point of sale, and it overwrites on every sync. But a merchant wants better photographs, longer descriptions and web-only items. Store those in the same rows and the next sync erases them. Store them nowhere and the online store reads like a spreadsheet. Most integrations in this category land on one side of that trade or the other.

How it was built

In order, and why.

  1. 01

    Map the POS

    What it emits, how often, what it silently omits, and what it does on a delete. Integration work fails on assumptions about the other system, so this came before any code.

  2. 02

    Two tables, one product

    POS data lands in immutable reference tables the sync owns completely; merchant customisations live in a parallel table keyed to the same product. The read layer merges them, so a sync can never clobber a custom description or image.

  3. 03

    Payments and identity

    Two gateways for routing and redundancy, and magic-link SSO so staff across locations sign in without another password to forget or leak.

  4. 04

    Sync and reconcile

    A background worker runs timestamped delta syncs on a per-store schedule with retries and backoff, orders push back to the POS automatically, and reconciliation reports anything the two sides disagree about.

The part that was actually hard

Making a sync that cannot destroy someone's work

The tempting design is one products table with the sync doing an upsert. It is simpler and it is wrong: the first time a merchant writes a good description, the next sync removes it. Splitting ownership at the field level — immutable POS-owned rows the sync controls entirely, merchant-owned rows it never touches, merged on read — lets the sync stay aggressive and simple, which is what a sync should be. The second half was doing that across four stores without hammering the POS API, which is what timestamped delta sync solved.

POS-owned

  • Price
  • Tax
  • Modifiers
  • Stock

Sync overwrites freely

merged on read

Merchant-owned

  • Description
  • Photography
  • Web-only items
  • SEO copy

Sync never touches

What it does

The features that carry it.

01

Delta sync

Changed records only, on a per-store schedule with retries and backoff. Fetching just what moved since the last run cut POS API traffic by roughly 80% while keeping the catalogue fresh.

02

Dual-table architecture

Merchant customisations live beside POS-owned data rather than inside it, so a sync overwrites what it should and nothing else.

03

Two payment gateways

NMI and Dejavoo, with routing between them, so a single processor problem is not a closed store.

04

Magic-link SSO

Keycloak-backed passwordless sign-in for staff across locations, which removes the shared-password problem that multi-site retail always has.

05

Multi-tenant RBAC

Three roles scoped per location across four stores, enforced server side rather than by hiding navigation.

06

Reconciliation

Scheduled comparison between the storefront and the POS, naming the records that disagree rather than counting them.

Outcome

What actually changed.

01

80% fewer POS API calls

Timestamped delta sync replaced full catalogue reloads, fetching only what changed since the last run while keeping four stores current.

02

Merchant work survives the sync

Descriptions, photography and web-only items live in rows the sync never touches, so the aggressive sync and the merchant's edits stopped being in conflict.

03

Four locations, one catalogue

Per-store scheduling with retries and backoff, and orders pushing back to the point of sale automatically rather than being re-keyed.

In hindsight

What we would do differently.

The reconciliation job shipped after launch rather than with the first sync. Its first run surfaced three weeks of accumulated drift — all of it recoverable, none of it detected until then, and none of it visible to anybody in the meantime. Reconciliation is a day of work and it should be part of the first integration, not an improvement you make once you already trust the system.

Built with

The stack underneath.

Application
React, Node.js, TypeScript
Data
PostgreSQL, Redis, delta sync, reconciliation
Payments
NMI, Dejavoo gateway routing
Identity
Keycloak, magic-link SSO, multi-tenant RBAC

Where it stands today

Shipped and running. This is the build the POS and payments pages on this site are describing when they talk about idempotent writes and reconciliation — it is where those opinions came from.

Practice areas this evidences

Tell us what you’re trying to build.

If something here looks like the problem you have, say so and we’ll tell you how close it actually is.

Reply time

One business day, from an engineer

Based in

Orlando, Florida · serving the United States

What happens next

  • A reply within one business day, from an engineer
  • A thirty-minute call, with no qualifying call before it
  • A written scope and a fixed number, if it fits