Integration Hub Pro is a paid add-on to the free Integration Hub plugin for nopCommerce 4.80, 4.90 and 5.00. It adds:
- Filters and payloads: send each webhook only the events it needs, in the receiver's own format.
- Inbound webhooks: let an ERP, warehouse or accounting system push stock, prices, order statuses and shipments into the store.
- Alerts by email, Slack and Microsoft Teams.
- History: keep the delivery log up to a year, search inside messages, and export it as CSV.
Everything is under Configuration → Integration Hub → Pro. Pro needs the free plugin's release 4 or later.
Filters and payloads
Open Pro → Filters and payloads and choose a webhook, or use the "Filter and payload (Pro)" link on a webhook's own page.
Which events go out
An event goes to the webhook only when every condition matches. You can test these fields:
| Field | Values | Applies to |
|---|---|---|
| Store | Store IDs | Every event raised in a store |
| Order total | A number, in the primary currency (is at least, is at most) | Orders and shipments |
| Payment method | System names, such as Payments.CheckMoneyOrder | Orders and shipments |
| Shipping method | Names, as on the order | Orders and shipments |
| Billing country, Shipping country | Two-letter ISO codes | Orders, shipments, customers |
| Customer role | System names, such as Registered | Orders, shipments, customers |
| Product category, Manufacturer, Vendor | IDs | Orders and shipments (any item), products, stock, reviews |
A condition about something an event doesn't have, such as a payment method on a product, doesn't apply to that event. Shipments use their order's details. The rule page lists the values the store has.
Payload templates
Without a template the webhook gets the standard message (see the webhook reference). A Liquid template replaces
the whole body. It can use the standard message's fields (id, type, occurredOnUtc, store, data, context)
and target (id, name). The template must write a JSON object:
{
"orderNumber": {{ data.number | json }},
"status": {{ data.status | lookup: "status" | json }},
"total": {{ data.totals.total }},
"lines": [
{% for item in data.items %}
{ "sku": {{ item.sku | json }}, "qty": {{ item.quantity }} },
{% endfor %}
]
}- Put
| jsonafter text values, so quotes, accents and missing values (null) are written correctly. - Trailing commas and comments are allowed, which keeps loops simple.
- Lookup tables translate values into the receiver's codes. Write a table name in brackets, then
value = replacementlines, and use the table as{{ data.status | lookup: "status" }}. A value that isn't in the table stays as it is.
[status]
Pending = NEW
Processing = OPEN
Complete = DONEPreview builds the message for a real order, product or customer with the settings on the form, saved or not. It also says whether the event would pass the conditions.
If a template fails for a message, for example because it writes invalid JSON, the message is not sent in the standard shape. It counts as a failed attempt, is retried on the normal schedule, and the delivery log shows why. Fix the template and the waiting messages go out in order. "Send test" always sends the standard test message.
Templates run in a sandbox. They can't read files or other templates, or reach .NET objects. Loops are limited to 10,000 steps, each render to 2 seconds, and the output to 2 MB.
Inbound webhooks
Add an endpoint under Pro → Inbound for each system that sends changes. Each endpoint has:
- its own address,
https://{store}/api/cartenvo/v1/inbound/ihin_…; - its own secret;
- the message types it accepts;
- optionally, one store whose orders it may change.
Sending a message
Post JSON to the endpoint's address:
{ "id": "ERP-1001", "type": "stock.set", "data": { "lines": [ { "sku": "SHIRT-BLUE-L", "quantity": 12 } ] } }idis the ERP's own ID for the change, up to 200 characters. A message with an ID that was already applied is not applied again. The store answers with the first answer and the headerX-Cartenvo-Duplicate: true. A message that was rejected can be sent again with the same ID once it's fixed.typeis one of the types below, orpingto check the connection, the signature and the template.datahas the same shape as the matching sync API call.- Dry run: add
?dryRun=trueto the address, or"dryRun": trueto the body. The store checks everything and reports what would change, and changes nothing.
| Type | data | Sync API equivalent |
|---|---|---|
stock.set | lines of sku (or productId, combinationId, warehouseId) with quantity or adjustment; optional reason | POST stock |
price.set | lines of sku with price, oldPrice, productCost | POST prices |
order.status | the order, plus orderStatus, paymentStatus, notifyCustomer | POST orders/{id}/status |
shipment.add | the order, plus items, trackingNumber, ship, deliver, notifyCustomer | POST orders/{id}/shipments |
Name the order in one of three ways:
"orderId": 12;"orderNumber": "1012";"externalReference": {"system": "odoo", "reference": "SO001"}, the order's number in the ERP, saved through the sync API or an ERP connector.
If the endpoint has an ERP name set, "externalReference": "SO001" alone is enough.
Signing
Sign each call the same way the Hub signs its own webhooks:
X-Cartenvo-Timestampis the current Unix time.X-Cartenvo-Signatureissha256=followed by the hex HMAC-SHA256 of"{timestamp}.{body}", using the endpoint's secret.
The store accepts a timestamp up to 5 minutes from its own clock. For systems that can't sign, switch on "Also accept
the secret as a bearer token". They then send Authorization: Bearer {secret} instead. Calls must use https.
Reading the ERP's own format
When the ERP can't send the Hub's format, give the endpoint a Liquid template. The template reads body (the JSON
received), headers and endpoint, and writes an inbound message:
{
"id": {{ body.docNo | json }},
"type": "stock.set",
"data": { "lines": [
{% for i in body.items %}{ "sku": {{ i.code | json }}, "quantity": {{ i.onHand }} },{% endfor %}
] }
}Answers
| Status | Meaning |
|---|---|
| 200 | Applied, or a dry run. The body has status (applied, dryRun or ok) and the result, which has the same shape as the sync API's answer. |
| 400 | The JSON, the message or data isn't valid. detail says what's wrong. |
| 401 | Missing or wrong signature or token, or a timestamp that's too old. |
| 403 | The endpoint doesn't accept this type, or Pro has no active licence. |
| 404 | There's no endpoint at this address, it's switched off, or the order wasn't found. |
| 409 | The same ID is still being applied, or the order can't change that way. |
| 422 | The endpoint's template couldn't read the message. |
| 503 | The store is busy (Retry-After is set). Send the same message again. |
Errors use the sync API's problem format: type, title, status, detail and code. Every call is logged under
Pro → Inbound → Inbound log, with the body received and the answer sent.
Alerts
Under Pro → Alerts, choose where alerts go: email addresses, a Slack incoming webhook, a Teams workflow ("Post to a channel when a webhook request is received"), or any mix. Then choose when they're sent:
- A webhook has failed a number of attempts in a row (5 by default). This happens before its messages run out of retries. You get at most one alert per webhook every 6 hours, and a note when it works again.
- An API key is used from an address it hasn't used before. This is checked every 5 minutes.
- Inbound calls keep being refused: 10 calls with a wrong signature or token within an hour.
- Stock hasn't synced through the sync API or an inbound webhook for a number of hours.
"Send a test alert" sends to every channel and reports any channel that failed.
History
The free plugin keeps the delivery log for up to 90 days. With Pro, the limit under Settings goes up to 365 days.
Pro → History searches the log by text in the message, the answer or the error, by event ID (evt_…), webhook,
status, event, entity and dates. Export CSV downloads up to 100,000 rows, with or without the bodies. Inbound
calls are kept for the same number of days.
Licence
Paste the licence text (CIHP1.…) under Pro → Licence. The licence is checked inside the store, against each
store's domain, and Pro never calls home. On a development address (localhost, *.test, *.local) Pro works
without a licence.
Without an active licence, Pro changes nothing:
- webhooks get every event in the standard shape;
- inbound endpoints answer 403;
- alerts stop;
- the log limit goes back to 90 days.
Your settings are kept. A licence keeps working for 14 days after its expiry date.