Qomvia

Qomvia Market Protocol · v1

QMP: one discovery file, four order endpoints.

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.

1. Discovery

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 }
}

2. Signing

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>

3. Order endpoints

  • 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
}

4. Events back to Qomvia

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.

5. Conformance

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-secret

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.

Idempotent create

sessionId is the dedup key: a repeated POST for the same session returns the existing order, never a second charge.

Merchant of record

The buyer pays the merchant on paymentUrl. Qomvia never handles the goods payment — it only sees the paid event.

Feed-only fallback

No endpoint needed: a feed plus a coupon CSV and a redirect template list a shop without writing code.