Skip to main content

Tutorial: Building a Hybrid Integration Flow for Products and Images Between Akeneo PIM and PrestaShop using Flowlyze

1. Introduction

This document explains how to transfer product data from Akeneo PIM to PrestaShop using the Flowlyze integration platform. Since there is no native destination node for PrestaShop on Flowlyze, this integration is built using a custom HTTP request. This flow directly feeds a PrestaShop environment, fully automating product catalog management.

Video tutorial

💡 Tip: Watch the video below to see the flow step by step.

2. Prerequisites

  • An active Akeneo PIM account with API access.
  • A PrestaShop installation accessible via HTTP.
  • A Webservice Key generated from the PrestaShop Admin panel (required for Basic Auth).
  • An image_proxy.php file placed in the PrestaShop root directory to handle download/upload operations.
  • An active Flowlyze account.
  • Intermediate C# knowledge for data formatting and XML construction.

3. Integration Architecture

To bypass performance bottlenecks and C# sandbox limitations, the data flow occurs in the following stages:

  • Trigger: Flowlyze connects to Akeneo and retrieves raw product data.
  • Mapping: The raw data from Akeneo is mapped to PrestaShop's core fields using the Mapping node on Flowlyze.
  • Transformation and Product Creation (C# Controller): The C# script processes the JSON data, builds the XML payload, and sends it to PrestaShop. The product is saved in the database (without images).
  • HTTP destination: Because there is no native PrestaShop adapter, the product is sent with an HTTP destination.
  • Image Transfer (PHP Proxy): The C# script parses the new Product ID from the successful HTTP 201 response. It sends this ID and the Akeneo image asset codes as a JSON payload to the image_proxy.php file. The PHP file downloads the images and uploads them to the PrestaShop API.

4. Step-by-Step Implementation

Step 4.1: Configuring the Trigger in Akeneo PIM

First, establish a secure connection to your data source:

  1. In the Flowlyze workspace, add a new Source node and select Akeneo.
  2. Authenticate the connection using your Akeneo API credentials (Client ID, Secret, Username, Password).
  3. Define which product catalogs to fetch and the trigger frequency (scheduled or manual).

Step 4.2: Data Transformation and Mapping (Flowlyze)

PrestaShop's accepted data structure is more specific compared to other platforms, making the formatting step crucial:

  1. Mapping Action: Map the incoming base fields from Akeneo to PrestaShop's required fields (e.g., reference code, main category ID).
  2. Data Formatting Action: Utilize Flowlyze's C# environment to make the incoming data fully compatible with the PrestaShop API. Use your C# script to clean and prepare nested data structures like multi-language fields or tax rules.
// 1. CATEGORY MAPPING
System.Collections.Generic.Dictionary<string, string> categoryMap = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.OrdinalIgnoreCase)
{
{ "3d_printers", "10" },
{ "robotics", "11" },
{ "drones", "12" },
{ "industrial_tools", "13" }
};

var reference = Data.GetString("reference") ?? "DEFAULT-REF";
var nameValue = Data.GetString("name") ?? "Default Product Name";
var weight = Data.GetString("weight") ?? "0.0000";
var price = Data.GetString("price") ?? "0.00";

string idCategory = "2";
var categoryList = Data.GetListString("categories");
if (categoryList != null && categoryList.Count > 0)
{
foreach (var rawCategory in categoryList)
{
if (string.IsNullOrWhiteSpace(rawCategory)) continue;
foreach (var cat in categoryMap)
{
if (rawCategory.IndexOf(cat.Key, System.StringComparison.OrdinalIgnoreCase) >= 0)
{
idCategory = cat.Value;
break;
}
}
if (idCategory != "2") break;
}
}

var activeVal = Data.Get("active");
bool isActive = activeVal != null && System.Convert.ToBoolean(activeVal);
string activeStr = isActive ? "1" : "0";

string linkRewrite = nameValue.ToLower()
.Replace(" ", "-").Replace("ş", "s").Replace("ı", "i").Replace("ğ", "g")
.Replace("ö", "o").Replace("ç", "c").Replace("ü", "u").Replace("---", "-").Replace("--", "-");

// 2. CREATING THE PRESTASHOP XML TEMPLATE
string xmlOutput = $@"
<prestashop xmlns:xlink=""http://www.w3.org/1999/xlink"">
<product>
<id_category_default><![CDATA[{idCategory}]]></id_category_default>
<reference><![CDATA[{reference}]]></reference>
<price><![CDATA[{price}]]></price>
<weight><![CDATA[{weight}]]></weight>
<active><![CDATA[{activeStr}]]></active>
<state><![CDATA[1]]></state>
<name>
<language id=""1""><![CDATA[{nameValue}]]></language>
<language id=""2""><![CDATA[{nameValue}]]></language>
</name>
<link_rewrite>
<language id=""1""><![CDATA[{linkRewrite}]]></language>
<language id=""2""><![CDATA[{linkRewrite}]]></language>
</link_rewrite>
</product>
</prestashop>";

// 3. SENDING THE PRODUCT TO PRESTASHOP VIA HTTP REQUEST
var request = new CustomHttpRequest
{
Url = "{{prestashopURL}}/api/products",
Method = "POST",
Headers = new System.Collections.Generic.Dictionary<string, string>
{
{ "Content-Type", "application/xml" }
},
Body = new System.Text.StringBuilder(xmlOutput)
};

string apiKey = "{{Your_apiKey}}";
var authValue = System.Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{apiKey}:"));
request.Headers.Add("Authorization", "Basic " + authValue);

var response = Http.Execute(request);

if (response.Status >= 200 && response.Status < 300)
{
// 4. PARSING THE PRODUCT ID
string productId = "";
int startIdx = response.Body.IndexOf("<id><![CDATA[");
if (startIdx != -1)
{
startIdx += 13;
int endIdx = response.Body.IndexOf("]]></id>", startIdx);
if (endIdx != -1)
{
productId = response.Body.Substring(startIdx, endIdx - startIdx);
}
}

// 5. SENDING IMAGE CODES IN BULK TO THE PHP PROXY (acceleration step)
var imagesRaw = Data.GetListString("images");
if (!string.IsNullOrEmpty(productId) && imagesRaw != null && imagesRaw.Count > 0)
{
var validAssets = new System.Collections.Generic.List<string>();
foreach (var code in imagesRaw)
{
if (!string.IsNullOrWhiteSpace(code)) validAssets.Add(code);
}

if (validAssets.Count > 0)
{
string assetJson = "[\"" + string.Join("\",\"", validAssets) + "\"]";
string proxyUrl = "{{prestashopURL}}/image_proxy.php";
string jsonPayload = $"{{\"product_id\": \"{productId}\", \"assets\": {assetJson}}}";

var proxyRequest = new CustomHttpRequest
{
Url = proxyUrl,
Method = "POST",
Headers = new System.Collections.Generic.Dictionary<string, string>
{
{ "Content-Type", "application/json" }
},
Body = new System.Text.StringBuilder(jsonPayload)
};

var proxyResponse = Http.Execute(proxyRequest);
Logger.Log($"Proxy image operation - HTTP {proxyResponse.Status}: {proxyResponse.Body}");
}
}

return Return.Skip($"Success (HTTP {response.Status}): product created, image processing handled on the proxy side.");
}
else
{
return Return.Error($"PrestaShop error (HTTP {response.Status}): {response.Body}");
}

Step 4.3: Configuring the Action in PrestaShop

Since there is no native node, you need to manually configure the operation using an HTTP Request node:

  1. Authentication (Auth Config): Add an HTTP Request as the destination node. Set Auth Type to Basic Auth. Paste the Webservice Key obtained from PrestaShop into the Username field. Configure the password field according to your security requirements.
  2. Base Settings (Settings): Enter the PrestaShop URL ({{prestashopURL}}) into the Base URL field.
  3. Endpoint and Method: Set the Resource Path to /api/products for the specific resource. Since we are creating a new product, select POST as the Method.
  4. Request Behavior: Check the Send one request per message box to ensure each product is processed individually and without errors.
  5. Image Upload (Proxy): Once the product is successfully created, parse the new product ID using C# and send it along with the image references to the image_proxy.php endpoint. This proxy file directly transfers the images to PrestaShop as multipart/form-data within the server.
<?php
$data = json_decode(file_get_contents('php://input'), true);

if (!isset($data['assets']) || !isset($data['product_id'])) {
http_response_code(400);
die(json_encode(["status" => "error", "message" => "Missing parameters"]));
}

$productId = $data['product_id'];
$assets = $data['assets'];
$prestaApiKey = '{{Your_apiKey}}';

$akeneoDomain = "{{Your_akeneoDomain}}";
$clientId = "{{Your_clientId}}";
$clientSecret = "{{Your_clientSecret}}";
$username = "{{Your_username}}";
$password = "{{Your_password}}";

// Get the Akeneo token
$chToken = curl_init("$akeneoDomain/api/oauth/v1/token");
curl_setopt($chToken, CURLOPT_POST, true);
curl_setopt($chToken, CURLOPT_POSTFIELDS, http_build_query([
'grant_type' => 'password',
'username' => $username,
'password' => $password,
'client_id' => $clientId,
'client_secret' => $clientSecret
]));
curl_setopt($chToken, CURLOPT_RETURNTRANSFER, true);
$tokenRes = curl_exec($chToken);
curl_close($chToken);

$tokenData = json_decode($tokenRes, true);
$token = $tokenData['access_token'] ?? null;

if (!$token) {
http_response_code(500);
die(json_encode(["status" => "error", "message" => "Unable to obtain the Akeneo token.", "response" => $tokenRes]));
}

$results = [];
$tempPath = tempnam(sys_get_temp_dir(), 'img') . '.jpg';

foreach ($assets as $assetCode) {
if (empty($assetCode)) continue;

$chAsset = curl_init("$akeneoDomain/api/rest/v1/asset-families/imported_assets/assets/$assetCode");
curl_setopt($chAsset, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token", "Accept: application/json"]);
curl_setopt($chAsset, CURLOPT_RETURNTRANSFER, true);
$assetRes = curl_exec($chAsset);
$assetStatus = curl_getinfo($chAsset, CURLINFO_HTTP_CODE);
curl_close($chAsset);

$assetData = json_decode($assetRes, true);
$mediaLink = $assetData['values']['media_link'][0]['data'] ?? null;

if (!$mediaLink) {
$results[] = "$assetCode skipped: link could not be retrieved from Akeneo (HTTP $assetStatus).";
continue;
}

$chImg = curl_init($mediaLink);
$fp = fopen($tempPath, 'wb');
curl_setopt($chImg, CURLOPT_FILE, $fp);
curl_setopt($chImg, CURLOPT_HEADER, 0);
curl_setopt($chImg, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
curl_exec($chImg);
curl_close($chImg);

if (filesize($tempPath) > 0) {
// Upload to PrestaShop (URL and authentication updated)
$uploadUrl = "{{prestashopURL}}/api/images/products/$productId?ws_key=$prestaApiKey";
$chUpload = curl_init($uploadUrl);

curl_setopt($chUpload, CURLOPT_POST, true);
$cFile = curl_file_create($tempPath, 'image/jpeg', 'image.jpg');
curl_setopt($chUpload, CURLOPT_POSTFIELDS, ['image' => $cFile]);
curl_setopt($chUpload, CURLOPT_RETURNTRANSFER, true);
curl_setopt($chUpload, CURLOPT_FOLLOWLOCATION, true);

$upRes = curl_exec($chUpload);
$upStatus = curl_getinfo($chUpload, CURLINFO_HTTP_CODE);
curl_close($chUpload);

if ($upStatus >= 200 && $upStatus < 300) {
$results[] = "$assetCode added.";
} else {
$results[] = "$assetCode error: PrestaShop returned HTTP $upStatus. Detail: $upRes";
}
} else {
$results[] = "$assetCode error: the file was downloaded as 0 KB.";
}
}

if (file_exists($tempPath)) unlink($tempPath);
echo json_encode(["status" => "success", "details" => $results]);
?>

5. Testing and Deployment

To verify your custom HTTP flow:

  1. Start a test execution in Flowlyze using a single product payload from Akeneo.
  2. Check the Flowlyze Execution History logs to ensure the Mapping, Formatting, and HTTP POST steps completed without errors.
  3. If the logs indicate success, activate the flow for production. Once the flow runs live, log into your PrestaShop admin panel and manually verify that the products have been added with the correct category, SKU, and pricing.

6. Error Handling and Troubleshooting

  • Connection Issues: Verify that the Base URL configured in Flowlyze matches the correct PrestaShop URL ({{prestashopURL}}).
  • Authentication Errors (401 Unauthorized): Verify whether the Webservice Key under Basic Auth has the necessary permissions for the POST operation (write) in the PrestaShop panel.
  • Format Mismatches (400 Bad Request): The HTTP Request talks directly to the API and does not tolerate structural errors. Review the output generated by your C# code to ensure PrestaShop's required fields (e.g., missing language IDs) are not omitted.

7. Conclusion and Next Steps

Without needing a native integration node, you have built a fully automated data bridge between Akeneo and PrestaShop using HTTP Request capabilities on Flowlyze. As a next step, you can enhance your pipeline without any extra coding by integrating a Notification step (such as Slack or Email) to receive instant automated alerts whenever a product sync completes or if an error occurs.