Integration API
This API lets an online store connector read the Nadigit IMS catalog and stock, reserve stock for a cart and record orders. In return, Nadigit IMS notifies the connector with signed webhooks. It is the API used by the Bagisto connector, and the one to build a connector for another platform.
This page documents the Integration API only: the other Nadigit IMS APIs are reserved for the web console.
Before you start
- Enterprise plan.
- An integration declared and provisioned in E-commerce Integrations. Provisioning supplies the connector configuration. See E-commerce integration.
| Configuration field | Use |
|---|---|
backendUrl | Base address of the API: the paths below are appended to it |
keycloakTokenUrl | Token endpoint |
keycloakClientId, keycloakClientSecret | Connector credentials |
webhookSecret | Secret used to verify webhooks |
Authentication
The connector obtains an access token with the OAuth 2.0 client credentials
flow, then sends it in the Authorization header of every call.
curl -s -X POST "$KEYCLOAK_TOKEN_URL" \
-d grant_type=client_credentials \
-d client_id="$CLIENT_ID" \
-d client_secret="$CLIENT_SECRET"
GET /api/ecommerce/catalog HTTP/1.1
Authorization: Bearer <access_token>
- The token is short-lived (
expires_in, in seconds): keep it and renew it just before it expires, rather than on every call. - Scope. The connector can only reach the Integration API, and only for the warehouse and shop bound to the integration. The warehouse is never a request parameter: it is fixed by the connector's identity.
Conventions
- JSON exchanges, UTF-8 encoding.
- The SKU is the item's reference in Nadigit IMS, unique within the warehouse.
- Quantities are expressed in the item's selling unit, with decimals for
items sold by weight or measure (
fractional). - Amounts are in the organization's currency.
Endpoints
| Method | Path | Role |
|---|---|---|
GET | /api/ecommerce/catalog | Publishable catalog, paged |
GET | /api/ecommerce/catalog/{sku} | One item |
GET | /api/ecommerce/stock?skus=… | Stock for up to 200 items |
PUT | /api/ecommerce/reservations/{cartId} | Reserve stock for a cart |
POST | /api/ecommerce/orders | Record an order |
POST | /api/ecommerce/orders/{reference}/cancel | Cancel an order |
Catalog
GET /api/ecommerce/catalog?page=0&size=100
GET /api/ecommerce/catalog/ACC-POIG-BL
The catalog contains the active, physical items of the bound warehouse;
services are excluded. The list is paged (page from 0, size from 1 to 200,
100 by default) and sorted by SKU. An unknown, inactive or other-warehouse SKU
returns 404.
{
"sku": "ACC-POIG-BL",
"name": "Poignée de fenêtre à crémone – blanc",
"description": "…",
"category": "Accessoires",
"categoryPath": ["Accessoires"],
"price": 45.0,
"taxRate": 20.0,
"taxClass": "…",
"taxInclusive": false,
"allocatable": 146.0,
"measureUnit": "Unité",
"fractional": false,
"quantityPrecision": 0,
"primaryImageUrl": "https://…",
"imageUrls": ["https://…"],
"barcodes": ["…"]
}
The list returns these objects in a page (content, totalElements,
totalPages, number, size). The catalog has no "modified since" filter yet:
synchronize it in full, then rely on webhooks for changes.
Stock
GET /api/ecommerce/stock?skus=ACC-POIG-BL,SIL-NEU-280,UNKNOWN
[
{ "sku": "ACC-POIG-BL", "found": true, "sellable": 148.0, "reservedByOthers": 2.0, "allocatable": 146.0 },
{ "sku": "UNKNOWN", "found": false, "sellable": null, "reservedByOthers": null, "allocatable": null }
]
| Field | Meaning |
|---|---|
sellable | Sellable stock in the warehouse |
reservedByOthers | Quantity reserved by other carts or ongoing sales |
allocatable | What can still be sold: the quantity to display |
found | false for an unknown SKU: take it off sale |
Up to 200 SKUs per call, comma-separated.
Reservations
Reserving stock for a cart avoids selling online an item that is already gone. Call this endpoint on every cart change and just before payment.
PUT /api/ecommerce/reservations/cart-8841
Content-Type: application/json
{ "lines": [ { "sku": "ACC-POIG-BL", "quantity": 2 } ] }
204: the cart's reservation is replaced by these lines.409: payment must be blocked.
{
"code": "insufficient_stock",
"message": "…",
"lines": [ { "sku": "ACC-POIG-BL", "requested": 2, "allocatable": 1 } ]
}
code is insufficient_stock (not enough stock, line by line) or
reservations_disabled (soft reservations are disabled in Nadigit IMS). A
reservation expires by itself: an abandoned cart releases its stock without any
connector action.
Orders
POST /api/ecommerce/orders
Content-Type: application/json
{
"reference": "WEB-1000231",
"cartId": "cart-8841",
"orderDate": "2026-09-24T18:40:00",
"customer": { "email": "customer@example.ma", "firstName": "Salma", "lastName": "Idrissi", "phone": "+212600000000" },
"lines": [ { "sku": "ACC-POIG-BL", "quantity": 2, "unitPrice": 45.0 } ],
"declaredTotal": 108.0,
"paymentMethod": "card",
"paymentReference": "ch_3Q…"
}
| Field | Required | Note |
|---|---|---|
reference | Yes | Order reference on the store, 64 characters at most. Used as the uniqueness key |
cartId | No | The cart whose reservation is consumed |
customer | No | Customer found by e-mail, or created |
lines | Yes | SKUs and quantities. unitPrice is advisory |
shipping, declaredTotal | No | Advisory, compared with the recalculated total |
paymentMethod | No | See below |
paymentReference | No | Transaction reference, kept on the payment |
Behaviour:
- Nadigit IMS recalculates prices and taxes. If
declaredTotaldiffers, the order is accepted andpriceMismatchistrue. - The order takes stock out of the warehouse and is attached to the bound shop.
- Idempotency: sending the same
referenceagain returns the existing order (alreadyExisted: true) without creating a duplicate. A connector can safely replay an order. paymentMethod:card,wallet,stripe,paypal,online,transferorcashrecord a payment: the order is paid.cod(cash on delivery), an empty or unknown value record nothing: the order stays unpaid.
{
"reference": "WEB-1000231",
"status": "Processing",
"paymentStatus": "PAID",
"totalAmount": 108.0,
"dutyFreeAmount": 90.0,
"taxAmount": 18.0,
"alreadyExisted": false,
"priceMismatch": false,
"declaredTotal": 108.0
}
Cancellation
POST /api/ecommerce/orders/WEB-1000231/cancel
Cancels the order and puts its stock back in the warehouse. An order already
canceled returns success with alreadyExisted: true; a reference unknown in the
warehouse returns 404. Any refund is not automatic.
Webhooks
Nadigit IMS calls the connector at Storefront URL + Webhook path declared in
the integration, with POST, after each recorded change.
Events
product.updated: an item changed (stock, price, state).
{ "event": "product.updated", "action": "upsert", "sku": "ACC-POIG-BL", "occurredAt": "2026-09-24T18:41:07Z" }
action: "upsert": read the item again withGET /api/ecommerce/catalog/{sku}and apply it.action: "unpublish": take the item off sale (deactivated, sold out or no longer in the warehouse).
order.status: a web order changed status.
{ "event": "order.status", "reference": "WEB-1000231", "status": "Delivered", "paymentStatus": "PAID", "occurredAt": "2026-09-25T10:02:13Z" }
status | Meaning |
|---|---|
Processing | Being prepared (status on intake) |
Delivered, Completed | Delivered, completed |
Canceled | Canceled |
Return_Pending, Returned, Partial_Return | Return pending, returned, partially returned |
paymentStatus is UNPAID, PARTIALLY_PAID or PAID.
Verify the signature
Every webhook carries two headers:
X-Timestamp: the sending time, in seconds since 1 January 1970;X-Signature:HMAC-SHA256(webhookSecret, X-Timestamp + "." + body), in lowercase hexadecimal.
Recompute the signature on the raw body received, compare in constant time, and reject a timestamp that is too old (for example more than five minutes).
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $body, $webhookSecret);
if (!hash_equals($expected, $sig) || abs(time() - (int) $ts) > 300) {
http_response_code(401);
exit;
}
const crypto = require('crypto');
function isValid(rawBody, ts, sig, secret) {
const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
return sig.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
&& Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
}
Response and retries
Answer 2xx quickly, then process the webhook. On failure or no answer, Nadigit
IMS retries several times with increasing delays. Every attempt is logged on the
Nadigit IMS side. Process webhooks idempotently: the same event can arrive twice.
Error codes
| Code | Cause |
|---|---|
400 | Invalid request (missing or malformed field); the body details the fields |
401 | Token missing, expired or invalid: renew the token |
403 | Access denied, or feature not included in the installation's plan |
404 | SKU or order unknown in the bound warehouse |
409 | Insufficient stock or reservations disabled (see Reservations) |
503 | Installation without an active licence |
Errors other than 409 return a body of this form:
{ "timestamp": "…", "status": 403, "error": "Forbidden", "message": "…", "path": "/api/ecommerce/catalog", "errorCode": "…" }