Integrating with Alibaba Qimen from the WMS side
Notes from building a Qimen connector between Chinese OMS/ERP platforms and a 3PL's warehouse system, and the places the standard stops being standard.
Most of what is written about Qimen assumes you are the merchant. I was on the other end, building the connector that a 3PL’s warehouse system sits behind. These are my notes from that side. This reflects the API as of 2025.
What Qimen is
Qimen is a standard API and gateway between a merchant’s internal systems (ERP, OMS, WMS) and Alibaba’s marketplaces. The point is to kill point-to-point integrations. Every ERP does not need its own connection to every WMS. Each system integrates with Qimen once and speaks the same message format to everyone else.
It is run by Taobao and Tmall Group as part of the Taobao Open Platform. Alibaba Cloud is underneath it as infrastructure, not as the service itself.
In our case we connect our 3PL partner’s WMS to Qimen. That gives their clients a fast onboarding path. The client already runs a Qimen-connected OMS or ERP, that OMS is already wired to their Tmall shops and to the non-Taobao marketplaces (Red, Douyin, JD), and we plug the warehouse in at the end of it.
Getting in
Nothing happens until you are an approved ISV on the Taobao Open Platform, and that needs a Chinese business license. The application is heavier than a normal API signup. When we went through it we had to submit part of our system source code, a working login so they could run a compliance check, and our PRD and MRD. That was a few years before this post and the process has almost certainly moved since, but expect a review, not a form.
Approval gets you an appKey and appSecret. The signature scheme is documented clearly enough that I have nothing to add to it. There is also an official SDK, which I did not use. It does not read like Symfony code, and since we ship our side as a Symfony bundle I wanted full control over what goes on the wire. Raw HTTP, and I would make the same call again.
Two names that will confuse you
You are “the WMS,” but you are probably not the WMS. In Qimen’s vocabulary the warehouse side of the conversation is the WMS. Our system is not a warehouse management system. It is the connector between Qimen and the real WMS downstream. That middle box is what this post is about. From here on, when I say WMS I mean the real one downstream, not the Qimen sense of the word.
The “wms appkey” is not an appkey you ever receive. You hand your appKey to each
ERP/OMS that wants to route orders to you, and they will call it the “wms appkey” because
from where they sit that is what it is. But the appkey that arrives on an inbound request
is theirs, not yours. So routing works on the pair (their appkey, customerId), where
customerId is a tenant discriminator we assign when they first connect. That pair
identifies which of the 3PL’s clients the message belongs to. The warehouseId in the
payload then picks the warehouse.
We provision one connector per tenant per connected ERP/OMS. Three tenants coming in through the same OMS means three Qimen connectors on our side.
The interfaces
Sixteen in total, both directions. The prefix tells you which way the message is going.
Because we are the WMS side, the method in an inbound payload arrives bare, without the
taobao.qimen prefix. We only use the prefixed form when we are the ones calling the API.
Inbound, Qimen calls us:
| Interface | Method |
|---|---|
| 商品同步接口 | singleitem.synchronize |
| 入库单创建接口 | entryorder.create |
| 退货入库单创建接口 | returnorder.create |
| 出库单创建接口 | stockout.create |
| 发货单创建接口 | deliveryorder.create |
| 单据取消接口 | order.cancel |
| 单据挂起(恢复)接口 | order.pending |
| 库存查询接口(多商品) | inventory.query |
| 库存查询接口(多条件) | stock.query |
Outbound, we call Qimen:
| Interface | Method |
|---|---|
| 入库单确认接口 | taobao.qimen.entryorder.confirm |
| 退货入库单确认接口 | taobao.qimen.returnorder.confirm |
| 出库单确认接口 | taobao.qimen.stockout.confirm |
| 发货单确认接口 | taobao.qimen.deliveryorder.confirm |
| 订单SN通知接口 | taobao.qimen.order.sn.report |
| 库存盘点通知接口 | taobao.qimen.inventory.report |
| 库存异动通知接口 | taobao.qimen.stockchange.report |
The inbound half is an endpoint you build on your own server, to their spec. Qimen posts a signed payload to it and you answer synchronously in their envelope. Be clear about that when you scope the work. You are running an API that a marketplace calls, which is a much bigger job than consuming a feed.
The standard standardizes the envelope, not the behavior
This is the thing I got wrong at the start.
Qimen is middleware. No retry, no deduplication, no enforced error vocabulary. Response codes and error codes are defined in the standard, but in practice vendors rarely implement them strictly, because nothing makes them. What the values inside the envelope mean is whatever the ERP on the other end decided.
Retries and idempotency are the sending system’s responsibility, which means they differ from one vendor to the next. You cannot rely on the sender for either. Handle it on your side.
There is also extendProps, a Map field that acts as a custom-field escape hatch. Every
vendor uses it differently, so a lot of the per-vendor work ends up in there.
Where vendors actually diverge
Most vendors implement about 90% of the standard. The integration work is entirely the other 10%.
The clearest example I can give: itemCode. One ERP sends EAN13 barcodes in it. Everyone
else sends the SKU code. The standard defines the field but never says what goes in it, so
both are right. That single difference is a per-vendor mapping rule forever.
Inventory is the same story at a larger scale. Two vendors can both call inventory.query
and mean different things by it. One treats the response as a stock take, comparing and
then processing the gaps. Another treats it as a snapshot and only compares. Same
interface, different operational meaning. The standard does not say what the interface is
for, so both are correct.
Datetime was not a problem, for what it is worth. It is always China time. There were no quantity or unit convention surprises either.
How we handle it: classification per vendor, config-driven field mapping, normalize at the edge. Plus some dirty conditionals that became tech debt. That is the honest version.
Sandbox is three layers, and passing the first one proves very little
Going live means passing an automated API check in Qimen’s sandbox. That check tests the plain standard, so passing it tells you that you implemented Qimen correctly. It tells you nothing about whether you can talk to an actual customer.
The layers, each with its own definition of correct:
- Qimen’s sandbox check: the standard as written.
- The ERP/OMS’s own sandbox: the standard as that vendor implements it.
- Production: the same, plus real data.
You redo the sandbox connection check for each new ERP/OMS you onboard, which is useful, but layer one never gets any harder and layers two and three are where the time goes.
There was no dramatic outage to report. The standard is clear enough that most things work. When something does not, the fix is usually a direct conversation with the ERP’s IT team, not a support ticket. Occasionally the middleware itself has a problem and you escalate to the Taobao Open Platform support team. That is worth knowing, because the instinct when a call fails is always to assume you are the one who is wrong.
How I would design it now
I am not going to walk through our implementation. This is the shape I would recommend if
I were starting fresh, kept vendor-neutral, scoped to one flow: deliveryorder.create
inbound and deliveryorder.confirm outbound, plus the cancellation edge case.
Receive. The inbound endpoint does as little as possible: verify the signature,
validate the envelope, resolve the tenant from (appkey, customerId) and the warehouse
from warehouseId, then persist the raw request exactly as it arrived before anything
touches it. That stored payload is your only record of what the vendor actually sent, as
opposed to what your mapping layer made of it, and you will want it.
Then pick a processing model. Qimen always expects a synchronous response. That constraint does not go away, so it shapes both options.
Synchronous. Do the work inline and answer with the real outcome. The response says what actually happened, and it is the simplest thing to build. It holds until volume grows, and then a slow downstream WMS turns into slow responses on an endpoint whose caller you do not control.
Accept then queue. Acknowledge immediately, hand the message to a worker. This is what we moved to once we had onboarded enough customers, and it gave us roughly 10x the processing throughput. If I started again I would build it this way from the beginning.
The catch is that the response stops meaning “done” and starts meaning “accepted,” and Qimen’s envelope was not designed to say that. So decide deliberately what you validate before answering: signature, tenant resolution, required fields, anything cheap enough to check inline. Anything that fails after that point fails after you already said yes, and needs another route back to the vendor. If you ack before you have validated, a failure downstream has nowhere to go.
Sort out order unicity early. The ERP and the WMS each have their own identifier for the same order, and you should decide how they reconcile rather than find out later. One approach that has been enough for us: treat the vendor’s order code as the key at the boundary, scoped by tenant since codes are only unique within a vendor’s own world, and store the pairing with your internal reference the moment the order lands. Downstream code then looks up the mapping instead of inferring it. Every system will be shaped differently here, so take this as one workable answer rather than the answer.
Normalize in the worker. The per-vendor mapping belongs in one place, applied once, producing an internal representation the rest of the system understands. Downstream code should never know which vendor a message came from.
Confirm with your own retry. Outbound confirmations need backoff and a dead letter path that a human sees. The receiving ERP may or may not have retry logic. Yours is the only retry you control.
The cancellation race
order.cancel is where the design gets tested, because the message can arrive at any
point in the physical process. Whether cancellation is allowed at all is configuration,
and the China default is that it is allowed up until Shipped.
If the WMS has already confirmed the shipment, we block the cancel. Always. The stock is out and the parcel is moving. The cancellation is then handled on the ERP/OMS side, and the warehouse creates a return order to bring the stock back and match the ERP.
If the cancel arrives before the WMS has confirmed, we mark the order Cancel requested
and wait. If the WMS then confirms the shipment anyway, we ship it in the system. The WMS
is the source of truth and the system is never allowed to contradict it. What we do
instead is raise an alert, so the warehouse can still cancel the courier pickup in time.
The alerting that makes this work is a system of its own and I have left it out here.
What I would do differently
Design first. I would write a proper spec before writing code, and I would treat this as a system design problem rather than an API integration problem, because that is what it turned out to be.
Get on a call with the ERP/OMS IT team at the beginning, not at the point where something is broken in production. Every vendor has a dialect and they will tell you what it is if you ask early.
And do not trust the two things I assumed going in. A standard does not mean everyone behaves the same way. And passing Qimen’s sandbox does not mean passing a vendor’s sandbox, and neither of them means production.