Skip to Content

Dropshipper API

A B2B Product-Feed API in Two and a Half Weeks: NestJS, RabbitMQ and a Webhook Outbox
25 September 2026 by

Client: International Salon Supplies

Role: Full-Stack Engineer (sole developer)

Period: Mar 2025 – Jun 2025

Status: Production

Tech stack: NestJS 11, TypeORM, PostgreSQL, RabbitMQ, Swagger / OpenAPI, Next.js 15, React, TanStack Query, Tailwind CSS, shadcn/ui, Docker, Caddy

An API that lets registered dropshippers and distributors pull live product, price and stock data, or receive webhooks when it changes. Pricing depends on each customer's ERP price tier. Built on the platform's RabbitMQ event stream, with a transactional webhook outbox, Swagger docs, throttling and an admin dashboard. The core was production-ready in about two and a half weeks.

The problem

ISS works with dropshippers and distributors who resell its catalogue. They needed accurate, current product data (descriptions, images, prices and stock) in their own systems. Emailing spreadsheets doesn't scale, and giving partners access to the ERP was never an option.

Architecture

Retail Express ERP ─► ISSWA sync engine ─► RabbitMQ
                                              │ manual ack, reject on failure
                                              ▼
                              NestJS consumer ─► Products / Inventory / Customers services
                                              │  normalise + derive fields
                                              ▼
                                         PostgreSQL
                              ├─► REST: GET /v1/products?modified_since=…  (API key, 300 req/min)
                              ├─► Swagger docs
                              └─► Webhook outbox: cron every minute → POST to partner URLs
                                  (7-day retention, manual retry in dashboard)
Next.js dashboard: users, webhooks, delivery events (with response viewer), API tokens
Caddy (TLS) in front of both

The API subscribes to the same event stream that powers the ISSWA 2.0 platform, so it had real-time data from day one without touching the ERP directly.

Engineering highlights

Pricing that knows who's asking

Each partner is linked to their ERP customer record and price groups. Distributors see the distributor price as their buy price and the standard sell price as the recommended retail price. Everyone else sees the sell price as their buy price and the RRP as recommended. The same endpoint returns the right commercial view for each caller.

Business flags derived once

A product is treated as unavailable if the ERP disables it, if web export is off, or if the "Wholesale Website" sales channel says no. That logic is computed once during ingestion (disabled_for_web), not re-implemented by every partner.

A transactional outbox for webhooks

When data changes, a webhook delivery row is created for each subscribed partner with a payload that respects their price tier. A cron job sends pending deliveries every minute and records the response. Admins can inspect any delivery and retry it from the dashboard. Nothing is posted inline during ingestion, so a slow partner endpoint can never block the queue.

Incremental pulls

modified_since matches when either the product or its inventory changed (via a join), so partners polling the API get stock-only changes too. Images from several ERP field groups are collected into a single list.

Safe defaults

  • Global validation rejects unknown fields (whitelist + forbidNonWhitelisted).
  • Requests are throttled at 300 per minute.
  • The first admin account is created from environment variables at startup.
  • Swagger documents the API-key scheme, so partners can self-serve.

Timeline

  • 17 Mar 2025: first commit
  • ~1 Apr 2025: auth, webhooks, events, containerisation, Swagger, throttling, "prepared for production"
  • Jun 2025: manufacturer SKU added for partner matching

By the numbers

  • ~2.9k lines of backend TypeScript, ~2.6k frontend (excluding generated UI components)
  • 35 commits, all mine

What I'd harden next

  • Move password hashing to argon2 or bcrypt, and store API keys hashed.
  • Add automatic webhook retries with exponential backoff (today, retries are manual).

It's better to say that openly than to pretend v1 was perfect. These are exactly the items a v2 roadmap should start with.