MarketplaceHub Docs Docs

Your own system (ERP or warehouse)

Make your own system the source of truth for price and stock: mint a credential, push changes, verify InventoryChanged webhooks, reconcile with ModifiedSince.

This page is the whole recipe for making your own system — an ERP, a WMS, a spreadsheet on a timer — the source of truth for price and stock. Follow it top to bottom and you will have a two-way integration: your system pushes changes in, and hears about every change it did not make.

Making your own system the source of truth for price and stock: mint a credential, name your system the owner, push changes, subscribe to hear about changes you did not make, verify the signature, and reconcile because delivery is best-effort SIX STEPS TO MAKE YOUR ERP THE OWNER OF PRICE AND STOCK 1 Mint a credential Grant it InventoryWrite and WebhooksWrite. Copy the secret — it is shown once. 2 Name your system the owner Until you do, MarketplaceHub still treats another channel as the source for these values. 3 Push price and stock POST the SKUs that moved, up to 500 per call. Every SKU comes back with its own result. 4 Hear what you didn't do Subscribe to InventoryChanged so a sale on any channel reaches your system too. 5 Verify the signature Your endpoint is a public URL. The HMAC is what proves the delivery came from us. 6 Reconcile anyway Delivery is best-effort, so a periodic full read is what keeps the two sides honest. Ignore the echo of your own push Your write comes back to you as an InventoryChanged event. Acting on it re-pushes the same value forever — match on the source and drop your own.
Six steps — and one loop to avoid.

You need an account with the API enabled. Everything below is plain HTTPS and HMAC; there is no SDK to install.

1. Mint a credential

In MarketplaceHub, open the account menu, then API → Credentials. Create a credential with these permissions:

  • PricingWrite — to push price and stock.
  • InventoryRead — to read back and reconcile.
  • WebhooksWrite — to manage your subscription.

The secret is shown once and stored hashed. Copy it before leaving the page.

Then exchange it for a token, as Getting started describes:

curl -X POST https://identity.marketplacehub.com/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=mh_YourClientId" \
  -d "client_secret=YourSecret" \
  -d "scope=InventoryRead PricingWrite WebhooksWrite"

2. Tell MarketplaceHub your system owns price and stock

Open Data Flow in the app, press Change on Price, and choose My own system. Do the same for Stock. See Source of truth for what that changes.

This step is not optional and it is not cosmetic. Until you do it, a marketplace may still own those values, and anything you push will be overwritten the next time that marketplace syncs — the API will tell you so in the response, but it will still happen.

3. Push price and stock

curl -X POST https://api.marketplacehub.com/api/inventory/price-quantity \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "Items": [
          { "Sku": "WIDGET-RED-L", "Price": 24.99, "Quantity": 12 },
          { "Sku": "WIDGET-BLU-L", "Quantity": 0 }
        ]
      }'
  • Up to 500 items per request. The changes are applied while you wait, so you are told which SKUs landed.
  • Price and Quantity are both optional. Omitting one leaves it alone — null means "not provided", never "set to zero". Send "Quantity": 0 when you mean nothing in stock.
  • A SKU repeated in one request collapses to its last occurrence before anything is written.
  • PropagateToListings defaults to true, which schedules the outbound write to every marketplace. Set it to false when you are back-filling values the marketplaces already hold.

The response carries one result per distinct SKU, and a Warnings list. A warning here is the drift message described in Source of truth: your write was applied, and something else owns that data and will overwrite it. If step 2 was done properly you will never see one — so treat any warning as a configuration problem, not as noise.

4. Hear about changes you did not make

Subscribe to InventoryChanged:

curl -X POST https://api.marketplacehub.com/api/webhooks/Create \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "Url": "https://your-erp.example.com/hooks/marketplacehub",
        "EventTypes": ["InventoryChanged"]
      }'

Keep the Secret from the response. It is shown once and cannot be retrieved.

The event fires whenever price or stock actually moves in MarketplaceHub through the price-and-stock seam, whatever moved it — a file import, a marketplace sync, or your own push. See Webhooks for the body.

Ignoring your own push

Your writes fire the event too, so a naive receiver will apply its own change back to itself. The body tells you who wrote it:

"source": { "writer": "MerchantPush", "channel": "Api", "userPlatformId": null }

writer is MasterMirror when a marketplace sync produced the change and MerchantPush when somebody did something. channel says which path wrote: Api for a call to this API, FileImport for a price-and-stock file import, Marketplace for a sync, Assistant for a change the merchant confirmed in MarketplaceHub's Agent Bot, Mcp for a change an AI assistant made through MarketplaceHub's MCP tools, Rollback for a change a person made by undoing an earlier batch, Manual for a stock figure the merchant typed on MarketplaceHub's Products screen. Ignore channel == "Api" and act on everything else — that drops your own pushes and still hears about the merchant's file imports, Agent Bot changes and assistants' changes, which writer alone could not tell apart.

Two cases still deserve a value comparison: a second system writing through the API arrives as Api too, and a delivery can be retried. Apply the event only where the incoming figure differs from what your system already holds — that makes the receiver idempotent either way.

5. Verify the signature

Every delivery carries these headers:

HeaderCarries
X-MH-Signaturev1= followed by the lower-case hex HMAC-SHA256.
X-MH-TimestampUnix seconds at the moment of signing.
X-MH-EventThe event name, e.g. InventoryChanged.
X-MH-Delivery-IdStable across retries of the same delivery — use it to deduplicate.
X-MH-Attempt1 for the first try, then 2, 3…

The signature covers timestamp + "." + body, not the body alone. That is what stops a captured delivery being replayed at you later.

import hmac, hashlib, time

def verify(secret, timestamp_header, raw_body, signature_header, tolerance=300):
    if abs(time.time() - int(timestamp_header)) > tolerance:
        return False                      # too old, or too far in the future
    signed = f"{timestamp_header}.{raw_body}"
    expected = "v1=" + hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header)
  • Use the raw request body, byte for byte. Parsing the JSON and re-serialising it will change the bytes and the signature will not match.
  • Compare in constant time (compare_digest above), not with ==.
  • Reject anything more than 300 seconds old. We use the same tolerance.

Answer 2xx quickly. Do the work afterwards, on your own queue — a receiver that finishes its processing before replying is a receiver that times out under load and gets retried.

6. Reconcile, because delivery is best-effort

Webhooks are a notification, not a guarantee. A delivery is retried on failure, but six consecutive failures disable the subscription — and a subscription that is off is silent, not noisy. If your endpoint is down for a maintenance window longer than the retries cover, you will miss changes and nothing will tell you.

So run a reconciliation pass on a schedule. POST /api/products/List takes a ModifiedSince cursor:

curl -X POST https://api.marketplacehub.com/api/products/List \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "ModifiedSince": "2026-09-07T00:00:00Z", "Limit": 200, "Offset": 0 }'

Supplying ModifiedSince also orders the page oldest change first, which is what makes the walk resumable: take the LastModifiedDate of the last product on the page and use it as the next call's ModifiedSince. Without an order, a product changed mid-walk moves between pages and can be read twice or skipped — and the one it skips is exactly the row the pass exists to repair.

A reasonable rhythm: act on webhooks for freshness, and reconcile hourly with a ModifiedSince a little older than your last successful pass. Overlap is harmless if your receiver is idempotent, and step 4 already made it so.

What the response carries

Each product carries Sku, Quantity, Price, LastModifiedDate and its Listings — so a reconciliation pass can check both of the things your system owns.

Price is nullable, and the null means something: it is the price MarketplaceHub holds, and a product that has never had one has null rather than 0. Do not read a null as free.

It is also the hub's figure, not what a marketplace is currently listed at. A channel that carries a price of its own keeps it — MarketplaceHub's price is the fallback beneath it — so a null here does not mean your listings have no price, and a value here does not mean every listing shows it. If you need per-listing figures, the marketplace is the place to read them.

Check the subscription's health while you are there — POST /api/webhooks/List returns ConsecutiveFailures, LastSuccessDate and whether it is still active.

Putting it together

  1. Your system pushes a change → POST /api/inventory/price-quantity.
  2. MarketplaceHub stores it and schedules the write to every marketplace.
  3. Anything that changes price or stock — including your push — fires InventoryChanged.
  4. Your receiver verifies the signature, ignores channel == "Api", and applies the rest.
  5. An hourly ModifiedSince pass closes anything the webhooks missed.