Technical guides with real code, not empty promises. Each integration covers two things: showing Heurix results instead of native search, and keeping your catalog in sync.
Honesty first, and the answer differs by platform. PrestaShop and WooCommerce each have a module in beta, provided on request with support during integration — see their pages. Shopify and Magento have the guide below only; the Shopify one has not yet been proven on a real store. None of the four installs from its platform's official store today, and the code that follows remains the route if you would rather wire search up yourself.
Exporting your catalog from an ERP or a PIM? Drop the file in your console, in CSV or XML: the structure is detected, field matching is suggested, and the rule pack is recommended based on your content. See how it works →
The relay (Cloudflare Worker):
// worker.js — déployé sur Cloudflare Workers (gratuit pour ce volume)
export default {
async fetch(request, env) {
const url = new URL(request.url);
const q = url.searchParams.get("q") || "";
const res = await fetch("https://api.heurix.fr/v1/index/" + env.HEURIX_CATALOG + "/search", {
method: "POST",
headers: {
"Authorization": "Bearer " + env.HEURIX_API_KEY, // secret Cloudflare, jamais exposé
"Content-Type": "application/json"
},
body: JSON.stringify({ q, limit: 20 })
});
return new Response(await res.text(), {
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "https://votre-boutique.myshopify.com" }
});
}
}
In the theme (Online Store 2.0), replace the rendering of sections/search-results.liquid with a call to this relay:
<div id="heurix-results"></div>
<script>
const params = new URLSearchParams(window.location.search);
fetch("https://votre-worker.workers.dev/search?q=" + encodeURIComponent(params.get("q") || ""))
.then(r => r.json())
.then(data => {
document.getElementById("heurix-results").innerHTML = data.hits.map(h =>
`<a href="/products/${h.product.handle}" class="heurix-hit">
<span>${h.product.name}</span><span>${h.product.price} €</span>
</a>`
).join("");
});
</script>
Register a Shopify webhook (Admin API or shopify.app.toml for a custom app) on the products/create, products/update, products/delete topics, pointed at the same Worker:
// Ajout dans worker.js : gestion du webhook Shopify
async function handleWebhook(request, env) {
const product = await request.json();
await fetch("https://api.heurix.fr/v1/index/" + env.HEURIX_CATALOG + "/items", {
method: "POST",
headers: { "Authorization": "Bearer " + env.HEURIX_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
rulepack: "mode",
items: [{
id: String(product.id),
name: product.title,
ref: product.variants?.[0]?.sku || "",
description: product.body_html?.replace(/<[^>]+>/g, "") || "",
stock: product.variants?.reduce((s, v) => s + (v.inventory_quantity || 0), 0) || 0,
handle: product.handle,
price: product.variants?.[0]?.price
}]
})
});
}
Remember to verify the Shopify webhook's HMAC signature (X-Shopify-Hmac-Sha256) before processing the payload, to avoid accepting forged requests.
In a custom module, hook displaySearchResults or override the search controller:
<?php
// modules/heurixsearch/heurixsearch.php
class HeurixSearch extends Module
{
public function search($query)
{
$ch = curl_init('https://api.heurix.fr/v1/index/' . Configuration::get('HEURIX_CATALOG') . '/search');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . Configuration::get('HEURIX_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['q' => $query, 'limit' => 24]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
return $response['hits'] ?? [];
}
}
In the results template (search.tpl), loop over these results instead of PrestaShop's native search.
Three hooks are enough to stay in sync in real time:
<?php
public function install()
{
return parent::install()
&& $this->registerHook('actionProductAdd')
&& $this->registerHook('actionProductUpdate')
&& $this->registerHook('actionProductDelete');
}
public function hookActionProductUpdate($params)
{
$product = $params['product'];
$this->pushToHeurix([
'id' => (string) $product->id,
'name' => $product->name,
'ref' => $product->reference,
'description' => strip_tags($product->description_short),
'stock' => (int) StockAvailable::getQuantityAvailableByProduct($product->id),
]);
}
private function pushToHeurix($item)
{
$ch = curl_init('https://api.heurix.fr/v1/index/' . Configuration::get('HEURIX_CATALOG') . '/items');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . Configuration::get('HEURIX_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['rulepack' => 'mode', 'items' => [$item]]),
]);
curl_exec($ch);
curl_close($ch);
}
wp_remote_post(), WordPress's native HTTP function, rather than raw cURL.
<?php
// Dans functions.php ou un plugin dédié
add_filter('woocommerce_product_query_meta_query', function ($meta_query, $query) {
if (!is_search()) return $meta_query;
$response = wp_remote_post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/search', [
'headers' => [
'Authorization' => 'Bearer ' . HEURIX_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode(['q' => get_search_query(), 'limit' => 24]),
]);
$hits = json_decode(wp_remote_retrieve_body($response), true)['hits'] ?? [];
$ids = array_map(fn($h) => (int) $h['product']['id'], $hits);
// Réordonne les résultats WooCommerce natifs selon le classement Heurix
$query->set('post__in', $ids ?: [0]);
$query->set('orderby', 'post__in');
return $meta_query;
}, 10, 2);
This approach has an advantage: it reuses WooCommerce's native display (product cards, existing filters), Heurix only reranks results — less template to rewrite.
<?php
add_action('woocommerce_update_product', 'heurix_sync_product');
add_action('woocommerce_new_product', 'heurix_sync_product');
function heurix_sync_product($product_id) {
$product = wc_get_product($product_id);
wp_remote_post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items', [
'headers' => [
'Authorization' => 'Bearer ' . HEURIX_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode([
'rulepack' => 'mode',
'items' => [[
'id' => (string) $product_id,
'name' => $product->get_name(),
'ref' => $product->get_sku(),
'description' => wp_strip_all_tags($product->get_short_description()),
'stock' => $product->get_stock_quantity() ?? 0,
]],
]),
]);
}
add_action('woocommerce_delete_product', function ($product_id) {
wp_remote_request('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items/' . $product_id, [
'method' => 'DELETE',
'headers' => ['Authorization' => 'Bearer ' . HEURIX_API_KEY],
]);
});
A custom module with a plugin on the search results block:
<?php
// app/code/Heurix/Search/Model/SearchClient.php
namespace Heurix\Search\Model;
use Magento\Framework\HTTP\Client\Curl;
class SearchClient
{
public function __construct(private Curl $curl) {}
public function search(string $query): array
{
$this->curl->addHeader('Authorization', 'Bearer ' . HEURIX_API_KEY);
$this->curl->addHeader('Content-Type', 'application/json');
$this->curl->post(
'https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/search',
json_encode(['q' => $query, 'limit' => 24])
);
$data = json_decode($this->curl->getBody(), true);
return $data['hits'] ?? [];
}
}
Inject this service into a plugin on Magento\CatalogSearch\Model\Layer\Filter, or directly replace the search results template with rendering based on this data.
An observer on the catalog's standard events:
<?php
// app/code/Heurix/Search/Observer/SyncProduct.php
namespace Heurix\Search\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
class SyncProduct implements ObserverInterface
{
public function __construct(private \Magento\Framework\HTTP\Client\Curl $curl) {}
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
$this->curl->addHeader('Authorization', 'Bearer ' . HEURIX_API_KEY);
$this->curl->addHeader('Content-Type', 'application/json');
$this->curl->post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items', json_encode([
'rulepack' => 'mode',
'items' => [[
'id' => (string) $product->getId(),
'name' => $product->getName(),
'ref' => $product->getSku(),
'stock' => (int) $product->getExtensionAttributes()?->getStockItem()?->getQty(),
]],
]));
}
}
Declared in events.xml on catalog_product_save_after and catalog_product_delete_after.
These guides are a technical starting point, not a turnkey module. If your setup is out of the ordinary, get in touch.