Signed both ways
Qomvia signs order calls with the merchant key; the shop signs events back with its Qomvia key. Same HMAC shape, both directions.
Qomvia Market Protocol · v1
A shop that speaks QMP answers agents natively: it publishes a discovery document, receives signed order requests and returns a payment URL. Feed-only shops work without writing a line.
Served at /.well-known/qomvia.json. Qomvia reads it during the dashboard connection check.
// https://shop.example/.well-known/qomvia.json
{
"qmp": "1",
"name": "Velo Zürich", // optional
"feed": { "url": "https://shop.example/feed.xml", "format": "google_xml" },
"orders": "https://shop.example/qomvia/orders",
"currency": "CHF",
"legal": { "sellerName": "Velo Zürich AG", "vatId": "CHE-123.456.789" }, // optional
"privacyPolicyUrl": "https://shop.example/privacy", // optional
"shipping": { "countries": ["CH", "DE", "AT"], "deliveryDaysMin": 2, "deliveryDaysMax": 4 },
"capabilities": ["orders.list", "events", "fulfillment"], // optional
"events": true, // optional — the shop pushes events
"terms": { "shippingPolicyUrl": "https://shop.example/shipping", "returnWindowDays": 30 }
}Every call in both directions carries a Bearer key and an HMAC-SHA256 signature over the timestamp and raw body.
signature = HMAC-SHA256(key, "<timestamp>.<raw request body>")
Qomvia-Timestamp: unix seconds, skew ≤ 300 s
Qomvia-Signature: v1=<hex lowercase signature>
Authorization: Bearer <merchant key>POST {orders}Create an order for an agent session. 201 with orderId + paymentUrl; retry with same sessionId returns the existing order.GET {orders}/{id}Order status: pending | paid | refunded | cancelled, plus totalCents, refundedCents, paidAt and optional fulfillment {status, carrier?, trackingNumber?, trackingUrl?}.GET {orders}?code=QV-&since=List orders by code prefix and timestamp — reconciliation for agents and dashboards.DELETE {orders}/{id}Cancel an unpaid order before payment. 204; 404 when unknown or already paid.POST {orders}
Authorization: Bearer <merchant key>
Qomvia-Timestamp: 1759056000
Qomvia-Signature: v1=<hex>
{
"sessionId": "ses_9f2e",
"code": "QV-4K9M2XPL",
"items": [
{
"gtin": "7612345678901",
"sku": "HELMET-1",
"variantId": "451002",
"title": "Velohelm Pro",
"qty": 1,
"unitCents": 8540,
"listCents": 8990,
"url": "https://shop.example/p/velohelm-pro"
}
],
"discountCents": 450,
"currency": "CHF",
"shippingCents": 690,
"buyer": { "email": "[email protected]", "name": "Buy Er" },
"shipTo": { "name": "Buy Er", "line1": "Weg 1", "zip": "8000", "city": "Zürich", "country": "CH" },
"expiresAt": "2026-09-28T12:30:00Z",
"note": "Qomvia Market order · code QV-4K9M2XPL"
}
→ 201
{
"orderId": "ord_1842",
"paymentUrl": "https://shop.example/pay/ord_1842",
"orderUrl": "https://shop.example/orders/ord_1842", // optional status page
"totalCents": 9230,
"shippingCents": 690,
"taxCents": 0,
"expiresAt": "2026-09-28T12:30:00Z",
"messages": [] // optional [{code, message, param?}] — e.g. stock notes
}When payment settles, refunds or is cancelled, the shop posts a signed event to Qomvia — the sessionId from the order create identifies the session.
// merchant → Qomvia, signed the same way with the Qomvia key
POST /api/v1/market/orders/{sessionId}/events
{
"event": "paid", // paid | refunded | cancelled | shipped | delivered
"orderId": "ord_1842",
"amountCents": 9230, // paid/refunded amount
"reason": "item returned", // optional, for refunded/cancelled
"fulfillment": { // optional, for shipped/delivered
"status": "shipped", "carrier": "Die Post", "trackingNumber": "99.60.12345"
},
"at": "2026-09-28T12:35:10Z"
}401Missing or wrong Bearer key.403Timestamp outside the 300-second window or signature mismatch.404Unknown order id.409Conflict — e.g. deleting a paid order.422Body fails validation — wrong shapes, unsupported item.Ship a shop? Run the reference checks — a discovery fetch plus a full create → get → list → delete cycle.
npm run qmp:shop -- ./fixtures/market/velo.csv 4010 # reference shop
node scripts/qmp-conformance.mjs http://localhost:4010 shop-secretQomvia signs order calls with the merchant key; the shop signs events back with its Qomvia key. Same HMAC shape, both directions.
sessionId is the dedup key: a repeated POST for the same session returns the existing order, never a second charge.
The buyer pays the merchant on paymentUrl. Qomvia never handles the goods payment — it only sees the paid event.
No endpoint needed: a feed plus a coupon CSV and a redirect template list a shop without writing code.