You sell wholesale promo merchandise. Your retail catalogue lives on WooCommerce, Shopify or a custom WordPress stack. It works. The retail side runs beautifully. Then a wholesale partner asks the question that breaks everything:
What they're asking for is an API. What most suppliers do in response is one of three bad things:
- Build a manual export pipeline — one CSV per customer per week, by hand, drifting out of date the moment it lands.
- Expose their WooCommerce or Shopify REST API directly to the customer — turning their CMS into a public read endpoint, with all the security and contract problems that creates.
- Hire a developer to build a bespoke integration per customer — and discover six months later they're maintaining 14 different one-off integrations.
None of those scale. Each adds drag every time a new wholesale customer signs. Each one limits the business by spreadsheet maintenance instead of sales effort. (We've written a parallel piece on the same custom-vs-templated trade-off for agency client portals — read it here.)
This article walks through the architecture pattern we use at AppBox when a promo merch supplier needs to stop doing manual exports and start behaving like a real wholesale platform — pulled from a recent build for a wholesale operation we'll call SwaggMerch (19,000+ SKUs across two brands, two separate WordPress sites, a growing roster of wholesale partners). The same pattern works for SanMar resellers, AlphaBroder distributors, PromoStandards-aligned suppliers, branded apparel wholesalers and anyone whose retail catalogue is the source of truth and whose wholesale operation has outgrown spreadsheets. If your front-of-house promo website is also part of the question, our promo websites guide covers the customer-facing side of the same problem.
The cardinal rule: do not expose your CMS
The single most important architectural decision in a wholesale API project is also the most counterintuitive one: your CMS must not be the API your customers consume. WooCommerce, Shopify, Magento, custom WordPress — all of them have REST APIs. All of them are tempting to plug a customer into directly. Don't. Here's why.
Reason 1: every change you make ripples instantly into customer code
The retail team renames a product attribute. A wholesale customer's API integration breaks at 3am. You didn't deploy a release. You didn't touch the API contract. Someone in retail just edited a product label. Without an isolation layer between the source of truth and the customer-facing endpoint, every internal change is a public release.
Reason 2: WooCommerce / Shopify aren't designed for high-volume read traffic
A WooCommerce REST endpoint serving a wholesale customer polling every 60 seconds is a database query colliding with every front-end retail page load. You'll see slow product pages, timeouts, cache invalidations, mysterious checkout errors. The CMS is built to serve humans, not machines.
Reason 3: per-customer pricing is impossible without an isolation layer
Your retail price is the price on the website. Your wholesale price for Customer A is 30% off list. Your wholesale price for Customer B is tier-based on annual spend. Your wholesale price for Customer C excludes three categories that are partner-locked. None of that lives cleanly inside WooCommerce. You need a layer where you can apply per-customer rules without touching the source.
Reason 4: API key lifecycle is impossible to do well at the CMS layer
When a wholesale customer's contract ends, you need to revoke their key cleanly. When a key leaks, you need to rotate it instantly. When you want usage analytics per customer, you need a layer that owns the authentication. Bolting that onto WooCommerce or Shopify means plugins, plugin conflicts, and a security surface area you don't want to maintain.
The architecture, in one diagram
Here is the shape of every supplier-API build we ship:
- Source of truth — your existing CMS (WooCommerce, Shopify, Magento, custom). Internal team works exactly as today.
- Mirror layer — a separate database (Supabase / Postgres) that holds a copy of every product, price and stock level.
- Sync mechanism — webhooks for real-time + a nightly reconciliation job for safety.
- Customer-facing API — Next.js endpoints, with auth, rate limiting, per-customer pricing logic, and versioning.
- Admin portal — where you control what each customer sees, manage pricing tiers, issue API keys, watch usage.
One direction, by design. Data only flows out from the CMS — nothing the API or portal does can corrupt the source catalogue. Customers never touch your CMS. Your internal team never touches the API. Each side gets the workflow they actually need.
Two sync mechanisms, running in parallel
A common mistake is to pick one sync strategy. Real-time webhooks are great until one fails silently. Nightly batch jobs are great until your customer asks why a product showed in stock at 9am and was already sold out at 9:05am. The right answer is to run both.
Live layer — webhooks
WooCommerce, Shopify and most modern CMSes fire webhooks on product, price and stock changes. A simple flow:
- 1. CMS fires an event when a product, price or stock value changes.
- 2. A Next.js endpoint receives the event, verifies the signature, and writes the change to the mirror database.
- 3. The change is reflected in the customer-facing API within seconds.
Latency is measured in seconds. This is the experience you're selling — wholesale customers see live numbers, not yesterday's CSV.
Safety layer — nightly reconciliation
Webhooks miss. Networks fail. Someone manually edits a product in the CMS while the webhook endpoint is briefly down. To guarantee nothing slips through, run a full reconciliation every night:
- 1. Pull every product, price and stock level from the CMS REST API.
- 2. Diff against the mirror database.
- 3. Correct any drift automatically. Log every correction so you can audit what happened.
This catches missed webhooks, manual CMS edits, and any deployment-induced gaps. Customers never see the mess underneath. Your support inbox stops getting "your stock is wrong" tickets.
Per-customer pricing, visibility and contracts
The mirror layer makes per-customer logic trivial. A row-level security policy in Postgres, a few simple rules tables, and the API can serve the right view to the right customer without ever modifying the source catalogue.
Three tables that solve 90% of wholesale logic
customers— one row per wholesale customer, with their tier, contract start/end, and metadata.customer_visibility— which categories, brands or specific products this customer can see. Default-deny is safer than default-allow.customer_pricing_rules— tier-based discount, fixed price overrides, contract pricing on specific SKUs.
When a request comes in with a customer's API key, the API joins these three tables against the mirror, returns only what's visible, and applies pricing on the way out. Every change to visibility or pricing happens in the portal — never in the CMS. The retail team never sees this layer at all.
The contract endpoints customers actually want
Wholesale partners don't want a generic "list every product" endpoint. They want predictable contract endpoints they can build against:
GET /v1/products— paginated, filterable by category / brand / SKU, returns only products visible to this customer with this customer's price.GET /v1/products/:sku— single product, full attributes, current stock, current price for this customer.GET /v1/stock— lightweight stock-only endpoint for high-frequency polling. SKU + on-hand. Designed to be cheap.GET /v1/pricing— bulk price lookup for a batch of SKUs. Saves the customer from making 1,000 single-product calls.GET /v1/changes?since=...— delta endpoint for incremental sync. The customer pulls only what changed since their last call.
Version everything from day one. /v1/ in the URL. New fields are additive. Removals require a new version. Customers will integrate against this contract and expect it to be stable for years.
Authentication and API key management
Don't use OAuth for B2B wholesale APIs. Don't use JWT. Use long-lived API keys, scoped per customer, with clean lifecycle controls. Wholesale partners' integration teams want a single header, not an OAuth dance.
- Issue keys per customer (never per user) — keys belong to the contract.
- Allow multiple active keys per customer for staged rotation. Old key + new key both valid during a 30-day overlap.
- Rate limit per key — 60 requests per minute per customer is generous for wholesale integrations and protects you from rogue scripts.
- Log every request to an audit table — timestamp, key ID, endpoint, status code, response time. Your portal reads from this for usage analytics.
- Surface a one-click revoke in the admin portal. When a contract ends, revoke is instant — and irreversible.
Outbound webhooks: the optional upgrade
Polling every minute is fine. Pushing changes to customers as they happen is better. Once the inbound webhook + reconciliation pipeline is solid, an outbound webhook layer is a relatively small add-on:
- Customer registers a webhook URL in the portal.
- When a product, price or stock change lands in the mirror, queue an outbound delivery to every customer who has visibility on that change.
- Sign every delivery with a per-customer secret. Retry with exponential backoff on failure. Dead-letter after N retries.
For most wholesale customers, daily polling against /v1/changes?since=... is more than enough. Outbound webhooks are a premium-tier feature — and a real differentiator versus competitors still emailing CSVs.
What this actually costs to build
A well-scoped supplier API + admin portal is not a six-month enterprise project. The real cost depends on three variables: how many CMSes you're mirroring (one source vs multiple brands), how many customers you want supported on day one (one pilot vs full rollout), and how much per-customer logic you need at launch.