Your own system (ERP or warehouse)
The end-to-end recipe for making your own system the source of truth for price and stock: mint a credential, push changes, subscribe to InventoryChanged, verify the signature, and 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.
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:
InventoryWrite— 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 InventoryWrite 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.
PriceandQuantityare both optional. Omitting one leaves it alone —nullmeans "not provided", never "set to zero". Send"Quantity": 0when you mean nothing in stock.- A SKU repeated in one request collapses to its last occurrence before anything is written.
PropagateToListingsdefaults totrue, which schedules the outbound write to every marketplace. Set it tofalsewhen 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, whatever moved it — a file import, a marketplace sync, a person editing in the app, 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", "userPlatformId": null }
writer is MasterMirror when a marketplace sync produced the change and MerchantPush for everything else — your API calls, the app, and file imports. If your system is the only thing writing through the API, act only on MasterMirror and ignore the rest.
If people also edit in the app and you want those changes, you cannot tell them apart from your own by writer alone. Compare the values instead: apply the event only where the incoming figure differs from what your system already holds. That is worth doing anyway — it makes the receiver idempotent, which matters because a delivery can be retried.
5. Verify the signature
Every delivery carries these headers:
| Header | Carries |
|---|---|
X-MH-Signature | v1= followed by the lower-case hex HMAC-SHA256. |
X-MH-Timestamp | Unix seconds at the moment of signing. |
X-MH-Event | The event name, e.g. InventoryChanged. |
X-MH-Delivery-Id | Stable across retries of the same delivery — use it to deduplicate. |
X-MH-Attempt | 1 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_digestabove), 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 this pass can and cannot check
Each product in the response carries Sku, Quantity, LastModifiedDate and its Listings. It does not carry the price.
So a reconciliation pass can confirm that stock matches, and can tell you that something about a product changed while you were not listening — but it cannot tell you the current price. If your system owns price and you need to be certain of it, treat the InventoryChanged events as the only price channel and watch your subscription's health closely, or re-push the prices you believe are correct: a push that agrees with what is already stored changes nothing and fires no event.
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
- Your system pushes a change →
POST /api/inventory/price-quantity. - MarketplaceHub stores it and schedules the write to every marketplace.
- Anything that changes price or stock — including your push — fires
InventoryChanged. - Your receiver verifies the signature, ignores
MerchantPush, and applies the rest. - An hourly
ModifiedSincepass closes anything the webhooks missed.