Custom Integration Documentation
This API lets 3rd party systems push product catalog data into our system. When products are created, updated, or removed in your shop, send them to our webhook endpoint and we build the chatbot's product catalog and knowledge library from them — the same way our native Shoptet / Shopify integrations do.
product:update (create or update products) and product:delete (remove products). Any other event type returns a 400 Bad Request.
Contact your system administrator to obtain your unique webhook URL. It shares the same webhook-key as your order-data channel. It looks like:
https://admin.jarabot.com/webhooks/product_update/{your-webhook-key}
/webhooks/product_update/{webhook_key}
| Header | Value | Required |
|---|---|---|
Content-Type |
application/json |
Yes |
Authorization |
Token {your-api-key} |
If enabled |
The Authorization header is required only when API-key validation is enabled for your connection. We strongly recommend enabling it for product push.
product:updateThe payload is an array of product objects. Each object is the full current state of one product, keyed by its sku — we replace any previously stored data for that SKU (no field-level merge). The example below mixes one fully-described product and one minimal (required-fields-only) product.
{
"webhook": {
"event": "product:update",
"payload": [
{
"sku": "NIKE-PEGASUS-40",
"external_id": "12345",
"title": "Nike Air Zoom Pegasus 40",
"url": "https://shop.example.cz/nike-pegasus-40",
"price": 3499.00,
"currency": "CZK",
"margin": 800.00,
"availability": 0,
"availability_description": "In stock",
"visible": true,
"short_description": "Versatile running shoe for daily training.",
"description": "<p>The Pegasus 40 brings snappier response and a lighter upper.</p>",
"image_url": "https://shop.example.cz/img/pegasus40.jpg",
"thumbnail_url": "https://shop.example.cz/img/pegasus40-thumb.jpg",
"brand": "Nike",
"categories": [
{ "title": "Shoes", "url": "https://shop.example.cz/shoes" },
{ "title": "Sports shoes", "url": "https://shop.example.cz/sports-shoes" },
{ "title": "Running shoes", "url": "https://shop.example.cz/running-shoes" }
],
"traits": {
"color": ["black"],
"surface": ["road"]
},
"variants": [
{ "sku": "NIKE-PEGASUS-40-42", "availability": 0, "params": { "size": "42" } },
{ "sku": "NIKE-PEGASUS-40-43", "availability": 5, "params": { "size": "43" } }
],
"gifts": ["Free sports socks"],
"ean": "0195869884532",
"last_modified": "2026-06-30T10:00:00Z"
},
{
"sku": "GEL-NIMBUS-26",
"title": "Asics Gel-Nimbus 26",
"url": "https://shop.example.cz/gel-nimbus-26",
"price": 4290,
"currency": "CZK",
"availability": 0
}
]
}
}
The minimum viable product is these six fields. It will index and be findable, but answers are thin without a description, image, and traits — send the recommended fields too.
| Field | Type | Required | Description |
|---|---|---|---|
sku |
String | Yes | Stable unique product identifier. Used as the upsert/dedup key. |
title |
String | Yes | Product name. |
url |
String | Yes | Product page URL (http/https). |
price |
Number | Yes | Final customer price including VAT, in currency. No conversion is applied. |
currency |
String | Yes | ISO-4217 3-letter code (e.g. "CZK", "EUR"). |
availability |
Integer | Yes | Days until available: 0 = in stock now, -1 = unavailable / out of stock, N = available in N days. An out-of-stock product stays in the catalog and is shown to the customer as unavailable; send visible: false to take it out. A missing availability is read as 0, so always send it. |
| Field | Type | Required | Description |
|---|---|---|---|
external_id |
String | No | Your stable product id if different from sku. Defaults to sku. |
description |
String | No | Long description. HTML allowed (stripped for catalog, kept for the knowledge library). Primary content for AI answers. |
short_description |
String | No | Teaser / summary. |
image_url |
String | No | Main product image URL. Used in product cards. |
thumbnail_url |
String | No | Thumbnail image URL. Defaults to image_url. |
brand |
String | No | Manufacturer / brand name. Searchable and facetable. |
categories |
Array<Object> | No | Category breadcrumb, broad → specific. See Categories below. |
availability_description |
String | No | Human-readable stock text shown to the customer (e.g. "In stock", "Ships in 3 days"). |
traits |
Object | No | Structured filterable attributes. See Traits below. |
variants |
Array<Object> | No | Per-variant rows (size/color combos). See Variants below. |
| Field | Type | Required | Description |
|---|---|---|---|
visible |
Boolean | No | Default true. false removes the product from the catalog and the knowledge library. We keep no copy of it, so make the product visible again by pushing it with visible: true. |
last_modified |
String | No | When the product last changed on your side (ISO-8601). Send it. Without it we rewrite and re-index the product's library document on every push, which makes a repeated full-catalog push much slower and more expensive. With it we skip a document that has not changed. |
margin |
Number | No | Price minus purchase price, in currency. Internal only, never shown to customers. Defaults to 0. |
purchase_price |
Number | No | Alternative to margin: we compute margin = price − purchase_price. |
ean / gtin |
String | No | Barcode. Indexed — a customer who pastes a barcode into the chat finds the product. A barcode match never outranks a word match. |
gifts |
Array<String> | No | Free items bundled with the purchase. Shown as a "Free gifts" block. |
An ordered array, broadest first and the most specific (leaf) last.
The whole path is the product's category, not the leaf name alone. Two categories named "Running shoes" under two different parents stay two categories. Every prefix of the path filters: a customer who narrows to "Shoes" still finds a product filed under "Shoes / Sports shoes / Running shoes". Send the same path for every product in a category, or the two spellings become two categories.
"categories": [
{ "title": "Shoes", "url": "https://shop.example.cz/shoes" },
{ "title": "Sports shoes", "url": "https://shop.example.cz/sports-shoes" },
{ "title": "Running shoes","url": "https://shop.example.cz/running-shoes" }
]
| Field | Type | Required | Description |
|---|---|---|---|
categories[].title |
String | Yes | Category name for this breadcrumb level. |
categories[].url |
String | No | Link to this category's page. We store the URL of the last (leaf) category only, and use it to link an applied category filter to your category page. URLs on the higher levels are accepted and ignored. |
An object mapping attribute name to an array of string values — always an array, even for a single value. Used for faceted filtering ("red shoes size 42"). Only send values that are actually buyable.
"traits": {
"color": ["red"],
"size": ["42", "43", "44"],
"material": ["leather"]
}
Optional per-variant rows. A buyable variant's params are folded into the product's filterable traits, so a variant-only attribute (e.g. shoe size) becomes searchable even when it isn't a top-level trait.
"variants": [
{ "sku": "SHOE-42", "availability": 0, "params": { "size": "42" } },
{ "sku": "SHOE-43", "availability": 5, "params": { "size": "43" } }
]
| Field | Type | Required | Description |
|---|---|---|---|
variants[].sku |
String | No | Variant SKU (distinct from the parent product sku). Accepted for your own reference; we do not store it. Only params and availability shape the catalog. |
variants[].availability |
Integer | No | Same convention as product availability. Decides whether the variant is buyable. Defaults to the product's availability. |
variants[].params |
Object | No | Attribute name → single value for this variant (e.g. { "size": "42" }). |
One request carries as many products as you want, and we answer 200 before we write them. The write itself is not free: each product updates the catalog and rewrites its library document.
last_modified so a repeated push skips the products that did not change.For a first import of a large catalog, tell us before you start so we can watch the queues.
product:deleteThe payload is an array of SKUs to remove. Deleted products disappear from the catalog and knowledge library.
{
"webhook": {
"event": "product:delete",
"payload": ["GEL-NIMBUS-26", "NIKE-PEGASUS-40"]
}
}
payloadproduct:update and product:delete are supported)Authorization token (when API-key validation is enabled)
product:delete with the SKU to take it out of the catalog and library.product:delete does.sku is the unique identifier. Keep it consistent across all webhook calls for the same product. Products are language-agnostic — one pushed product feeds every language the bot is configured for.
If your bot reads its products from an XML feed today (Heureka, Google), the switch to push is a swap, and we do it in one window with you. What you decide first:
sku the feed produced. The feed keys a product on its EAN, falling back to the feed item id. Push the same value and every product updates in place, with no gap in the catalog and no duplicates. Push a different identifier and the catalog has to be rebuilt from scratch.variants. Product names get better this way — a feed has to guess the shared name from the variant names.visible: false, or leave them out, if you want the old behaviour.curl -X POST https://admin.jarabot.com/webhooks/product_update/your-webhook-key \
-H "Content-Type: application/json" \
-H "Authorization: Token your-api-key" \
-d '{
"webhook": {
"event": "product:update",
"payload": [
{
"sku": "PROD-001",
"title": "Example Product",
"url": "https://shop.example.com/prod-001",
"price": 599.00,
"currency": "CZK",
"availability": 0
}
]
}
}'
curl -X POST https://admin.jarabot.com/webhooks/product_update/your-webhook-key \
-H "Content-Type: application/json" \
-H "Authorization: Token your-api-key" \
-d '{
"webhook": {
"event": "product:delete",
"payload": ["PROD-001", "PROD-002"]
}
}'
Last updated: 24 August 2026