Retail point of sale terminal connected via cloud APIs to real-time inventory servers and instant QR electronic invoice dispatch.
E-Commerce

Point of Sale (POS) API: Real-Time Omnichannel Inventory Sync and Fiscal Invoicing

Engineering REST and WebSocket APIs for modern retail POS systems. Offline-first synchronization, atomic inventory locks, and automated electronic invoicing.

Omnichannel commerce has radically transformed retail operations. Modern merchants operating brick-and-mortar stores alongside digital channels (e-commerce storefronts on Shopify or WooCommerce, regional marketplaces like Mercado Libre, and conversational commerce over WhatsApp) can no longer afford to manage warehouse stock in silos.

The classic operational disaster—where a customer buys the last pair of sneakers at a shopping mall register while an online buyer purchases the exact same unit one second later—triggers a cascading crisis: severe overselling, forced order cancellations, frustrated customers, and algorithm penalties on digital marketplaces. The technical backbone that prevents this chaos is an event-driven Point of Sale (POS) API with real-time bidirectional synchronization and offline-first resiliency.

💡 Executive Summary: A modern POS API coordinates bi-directional inventory and sales data between physical checkout terminals and cloud backends using lightweight WebSockets or webhooks. It incorporates an offline-first architecture with local write queues to keep checkout lanes moving without internet, deterministic stock conflict resolution, and decoupled background workers for mandatory fiscal invoicing (such as DIAN in Colombia or SAT in Mexico).


1. The Retail Dilemma: Batch Synchronization vs. Real-Time Event Streams

Historically, multi-store retailers relied on closed, on-premise POS systems that exported end-of-day CSV batches or ran nocturnal synchronization scripts. In today’s fast-moving e-commerce landscape, this legacy batch model is obsolete and hazardous:

Operational DimensionLegacy Batch POS (End-of-Day Sync)Real-Time Cloud POS API (DoneAPI)
Inventory Update Latency4 to 24 hours of synchronization driftSub-250 milliseconds across all omnichannel sales channels
Overselling Risk During PeaksExtremely high during flash sales or Black FridayZero: atomic inventory reservation locks at the database layer
Tolerance to Internet DropsLane keeps running, but stock drifts dangerouslyOffline-First: local persistence automatically reconciled upon reconnect
Electronic Invoice DispatchSlow, manual, or trapped in clumsy third-party softwareDirect API integration with instantaneous QR code and CUFE/UUID generation
Store Rollout ElasticityDemands costly on-premise database servers per storefront100% Cloud: spin up a new terminal with a browser or lightweight tablet app

2. Offline-First Architecture & Conflict Resolution

In many commercial centers and busy shopping corridors across Latin America and emerging markets, broadband connections suffer from micro-outages. A point-of-sale checkout system must never halt customer lines simply because an internet link drops.

The non-negotiable architectural baseline is the Offline-First Pattern with Deferred Reconciliation:

[In-Store POS Terminal (Hardware / Web / Tablet)]
      |
      +---> Is Internet Connectivity Active?
               |
               +--- YES ---> Send sale to Cloud REST API (Instant atomic stock deduction)
               |
               +--- NO  ---> Write sale locally (IndexedDB / SQLite embedded engine)
                             Queue cryptographically signed payload with monotonic timestamp
                             Print provisional fiscal customer receipt
                                   |
                                   v  (Upon connectivity restoration)
                             Flush outbound queue to /v1/pos/sync endpoint
                             Apply deterministic inventory reconciliation logic

Essential Rules to Prevent Inventory Discrepancies:

  1. Atomic Locking with SELECT ... FOR UPDATE: On the cloud relational database, inventory decrements must execute within isolated transaction boundaries to ensure two registers cannot deduct the same stock unit in parallel.
  2. Terminal-Generated UUIDs: Every sale receives an immutable UUID generated directly on the local cash register. If network retries resend the payload multiple times, the backend recognizes the idempotency key and rejects duplicate deductions.

3. Production Implementation: Fastify & TypeScript POS Sale Controller

The following module illustrates how to handle an incoming retail sale with schema enforcement, atomic inventory decrements, and deferred electronic invoice queuing:

import { z } from 'zod';
import { FastifyRequest, FastifyReply } from 'fastify';

// 1. Strict input validation schema for POS transactions
export const PosSaleItemSchema = z.object({
  sku: z.string().min(3),
  quantity: z.number().int().positive(),
  unitPrice: z.number().positive(),
  taxRate: z.number().min(0).max(1), // e.g. 0.19 for 19% VAT
});

export const PosSaleRequestSchema = z.object({
  terminalId: z.string(),
  cashierId: z.string(),
  transactionUuid: z.string().uuid(),
  paymentMethod: z.enum(['CASH', 'CREDIT_CARD', 'DEBIT_CARD', 'DIGITAL_WALLET']),
  items: z.array(PosSaleItemSchema).nonempty(),
  customerTaxId: z.string().optional(), // Customer tax identification for electronic billing
});

export type PosSaleRequest = z.infer<typeof PosSaleRequestSchema>;

// 2. POS Checkout Controller
export class PosSaleController {
  constructor(
    private db: {
      transaction: <T>(callback: (tx: any) => Promise<T>) => Promise<T>;
    },
    private invoiceQueue: { enqueueInvoice: (saleId: string) => Promise<void> }
  ) {}

  public async handleSale(request: FastifyRequest, reply: FastifyReply) {
    const parseResult = PosSaleRequestSchema.safeParse(request.body);
    if (!parseResult.success) {
      return reply.status(400).send({
        error: 'BAD_REQUEST',
        details: parseResult.error.issues,
      });
    }

    const sale = parseResult.data;

    try {
      // Execute atomic transaction within the relational engine
      const result = await this.db.transaction(async (tx) => {
        // 1. Idempotency verification: has this transactionUuid already been recorded?
        const existing = await tx.query(
          'SELECT id, invoice_number FROM sales WHERE transaction_uuid = $1',
          [sale.transactionUuid]
        );
        if (existing.rows.length > 0) {
          return { saleId: existing.rows[0].id, duplicated: true };
        }

        // 2. Decrement inventory with row-level locks preventing overselling
        for (const item of sale.items) {
          const stockResult = await tx.query(
            'UPDATE inventory SET stock = stock - $1 WHERE sku = $2 AND stock >= $1 RETURNING stock',
            [item.quantity, item.sku]
          );

          if (stockResult.rows.length === 0) {
            throw new Error(`INSUFFICIENT_STOCK: SKU ${item.sku} does not have enough inventory.`);
          }
        }

        // 3. Persist sale ledger entry
        const insertSale = await tx.query(
          'INSERT INTO sales (terminal_id, transaction_uuid, payment_method, total, created_at) VALUES ($1, $2, $3, $4, NOW()) RETURNING id',
          [sale.terminalId, sale.transactionUuid, sale.paymentMethod, this.calculateTotal(sale.items)]
        );

        return { saleId: insertSale.rows[0].id, duplicated: false };
      });

      // 4. If electronic billing is requested, enqueue background dispatch asynchronously
      if (sale.customerTaxId && !result.duplicated) {
        await this.invoiceQueue.enqueueInvoice(result.saleId);
      }

      return reply.status(result.duplicated ? 200 : 201).send({
        success: true,
        saleId: result.saleId,
        message: result.duplicated ? 'Transaction previously processed' : 'Sale confirmed successfully',
      });
    } catch (error: any) {
      return reply.status(409).send({
        error: 'SALE_FAILED',
        message: error.message,
      });
    }
  }

  private calculateTotal(items: Array<{ quantity: number; unitPrice: number }>): number {
    return items.reduce((acc, item) => acc + item.quantity * item.unitPrice, 0);
  }
}

4. Electronic Invoicing at the POS: DIAN (Colombia) and SAT (Mexico) Compliance

Across Latin America, printing a physical sales slip at the cash register is no longer sufficient: governments legally require generating electronic documents validated in real time against national tax authorities (DIAN in Colombia via Electronic POS Equivalent Documents, or SAT in Mexico via CFDI 4.0).

Architectural Best Practices for Fiscal Invoicing:

  1. Asynchronous Background Processing: Never freeze cashier checkout lanes while waiting for a synchronous HTTP handshake from government tax servers (which frequently take 3 to 10 seconds or suffer temporary cloud outages).
  2. Deterministic Offline QR Code Rendering: The POS engine should immediately calculate the fiscal transaction hash (such as Colombia’s CUFE or Mexico’s digital seal) locally using the prescribed cryptographic algorithms so thermal receipt printers can output the legal QR code without network delay.
  3. Resilient Retry Pipelines for Tax Outages: Sales are marked in an internal state of “Pending Fiscal Transmission” and processed by automated queue workers when tax authority gateways return to health.

Frequently Asked Questions (FAQ)

What hardware is required to connect a point of sale terminal to a cloud API?

Any modern client capable of running a modern web browser or lightweight mobile runtime (Android/iOS tablets, Windows/Linux PC registers, or dedicated smart POS devices like Sunmi or Pax) with standard local network connectivity to ESC/POS thermal printers via USB, Bluetooth, or TCP.

How are bulk price updates propagated from headquarters to physical store terminals?

Through event-driven push channels using WebSockets or Server-Sent Events (SSE). When merchandise managers modify prices in the central ERP, the backend broadcasts a catalog.updated event, instructing connected registers to invalidate their local in-memory cache and pull down fresh pricing.

Which transport protocol is optimal for POS: REST or gRPC?

Between the cash register terminal and the Cloud API Gateway, REST over HTTPS/JSON or WebSockets is the industry standard due to ease of browser debugging and proxy traversal. For internal microservices communication on the backend (between inventory and billing), gRPC provides superior serialization speeds and lower network bandwidth consumption.

Can digital weighing scales and barcode scanners interface directly with a web POS?

Yes. Handheld barcode scanners operate natively as standard HID keyboard emulators. For weight scales, web-based POS interfaces leverage the modern Web Serial API or connect to lightweight local background daemons written in Node.js or Go that parse COM port streams and expose them over a localhost WebSocket.


Conclusion: Transform Your Retail Operations

A robust Point of Sale API is the bridge that transforms a traditional retail store into a unified, agile omnichannel operation immune to inventory discrepancies. By adhering to offline-first principles, atomic database transactions, and decoupled electronic invoice queues, you guarantee that your retail checkout lanes never stop ringing up sales.

💬 Looking to Build or Integrate an Omnichannel POS API? At DoneAPI, we architect high-availability inventory systems, offline-first sync pipelines, and compliant fiscal invoicing connectors:

👉 Request Engineering Advisory via WhatsApp (+57 320 817 3939)

Herramientas de Inteligencia Artificial para emprendedores

Desbloquea tu arsenal de automatización.

Regístrate gratis y accede a plantillas para n8n y Make.com, packs de prompts probados para IA, y guías exclusivas diseñadas para escalar tu negocio digital.

Crear cuenta y obtén recursos gratis