Products Sync API
📦 Overview
The Products Sync API provides specialized endpoints for synchronizing product data between GeSmart ERP and PrestaShop. Unlike the general sync endpoints, these endpoints offer fine-grained control over product synchronization with support for extended fields including brands, images, and technical specifications.
Key Features
- Single Product Sync: Synchronize individual products with complete control
- Batch Synchronization: Efficiently sync multiple products in a single request
- Extended Fields Support: Manage brands, multiple images, and technical data
- Real-time Updates: Webhook integration for instant product updates from GeSmart
- Comprehensive Status Tracking: Monitor synchronization progress and outcomes
Use Cases
- Initial product catalog import from GeSmart ERP
- Real-time product updates triggered by ERP changes
- Bulk product data synchronization during migration
- Individual product corrections and updates
- Product deletion and inventory management
🔐 Authentication
All endpoints require JWT Bearer token authentication:
Authorization: Bearer YOUR_JWT_TOKEN
Obtain your JWT token from the /api/auth/login endpoint. See Authentication for details.
📥 Postman Collection
Test our API easily with our complete Postman collection containing all endpoints with pre-configured examples.
Download Collection
How to Import
- Open Postman
- Click Import in the top left
- Select the downloaded file
- Configure environment variables:
base_url: Your API URL (e.g.,https://your-domain.com)jwt_token: Your JWT authentication token
Required Environment Variables
| Variable | Description | Example |
|---|---|---|
base_url | Base API URL | https://your-domain.com |
jwt_token | JWT authentication token | Obtained from /api/auth/login |
📍 API Endpoints
- Single Product Sync
- Batch Sync
- Delete Product
- Sync Status
- GeSmart Webhook
POST /api/products/sync
Synchronizes a single product from GeSmart ERP to PrestaShop with support for extended fields.
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes |
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
operation | string | Yes | Operation type: create or update | Must be 'create' or 'update' |
reference | string | Yes | Unique product reference/SKU | Max 64 characters |
name | string | Yes | Product name | 1-255 characters |
description | string | No | Product description | Max 10000 characters |
price | number | Yes | Product price (tax excluded) | Must be >= 0 |
quantity | number | Yes | Available stock quantity | Must be >= 0, integer |
active | boolean | No | Product active status | Default: true |
categories | array | No | Array of category IDs | Each ID must exist in PrestaShop |
ean13 | string | No | EAN13 barcode | 13 characters or empty |
weight | number | No | Product weight in kg | Must be >= 0 |
brand | object | No | Brand information | See Brand structure |
images | array | No | Product images | Max 10 images |
technical_data | array | No | Technical specifications | Max 100 items |
Success Response (200 OK)
{
"success": true,
"message": "Product synchronized successfully",
"data": {
"productId": 123,
"reference": "PROD001",
"presta_id": 456,
"syncId": "sync_abc123xyz",
"operation": "create",
"imagesProcessed": 3,
"technicalDataProcessed": 15
}
}
Code Examples
- cURL
- JavaScript
- C#
- PHP
- Python
curl -X POST https://your-domain.com/api/products/sync \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operation": "create",
"reference": "PROD001",
"name": "Professional Coffee Machine",
"description": "High-end espresso machine",
"price": 1299.99,
"quantity": 50,
"active": true,
"categories": [5, 12],
"brand": {
"name": "CoffeePro"
}
}'
const syncProduct = async (productData) => {
const response = await fetch('https://your-domain.com/api/products/sync', {
method: 'POST',
headers: {
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
operation: 'create',
reference: 'PROD001',
name: 'Professional Coffee Machine',
description: 'High-end espresso machine',
price: 1299.99,
quantity: 50,
active: true,
categories: [5, 12],
brand: {
name: 'CoffeePro'
}
})
});
const result = await response.json();
if (result.success) {
console.log('Product synced:', result.data);
} else {
console.error('Sync failed:', result.error);
}
return result;
};
// Usage
syncProduct().catch(console.error);
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class ProductSyncClient
{
private readonly HttpClient _httpClient;
private readonly string _jwtToken;
public ProductSyncClient(string baseUrl, string jwtToken)
{
_httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
_jwtToken = jwtToken;
}
public async Task<ProductSyncResponse> SyncProductAsync(ProductSyncRequest product)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/api/products/sync");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _jwtToken);
var jsonContent = JsonSerializer.Serialize(product);
request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ProductSyncResponse>(responseContent);
}
}
// Usage
var client = new ProductSyncClient("https://your-domain.com", "YOUR_JWT_TOKEN");
var product = new ProductSyncRequest
{
Operation = "create",
Reference = "PROD001",
Name = "Professional Coffee Machine",
Price = 1299.99m,
Quantity = 50
};
var result = await client.SyncProductAsync(product);
Console.WriteLine($"Success: {result.Success}");
<?php
function syncProduct($productData, $jwtToken) {
$ch = curl_init('https://your-domain.com/api/products/sync');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $jwtToken,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($productData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return json_decode($response, true);
}
// Usage
$product = [
'operation' => 'create',
'reference' => 'PROD001',
'name' => 'Professional Coffee Machine',
'description' => 'High-end espresso machine',
'price' => 1299.99,
'quantity' => 50,
'active' => true,
'categories' => [5, 12],
'brand' => [
'name' => 'CoffeePro'
]
];
$result = syncProduct($product, 'YOUR_JWT_TOKEN');
if ($result['success']) {
echo "Product synced: " . $result['data']['productId'];
} else {
echo "Sync failed: " . $result['error'];
}
import requests
import json
def sync_product(product_data, jwt_token):
url = 'https://your-domain.com/api/products/sync'
headers = {
'Authorization': f'Bearer {jwt_token}',
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, json=product_data)
return response.json()
# Usage
product = {
'operation': 'create',
'reference': 'PROD001',
'name': 'Professional Coffee Machine',
'description': 'High-end espresso machine',
'price': 1299.99,
'quantity': 50,
'active': True,
'categories': [5, 12],
'brand': {
'name': 'CoffeePro'
}
}
result = sync_product(product, 'YOUR_JWT_TOKEN')
if result['success']:
print(f"Product synced: {result['data']['productId']}")
else:
print(f"Sync failed: {result['error']}")
POST /api/products/sync/batch
Synchronizes multiple products in a single request for efficient bulk operations.
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes |
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
products | array | Yes | Array of product objects (same structure as single sync) |
Success Response (200 OK)
{
"success": true,
"message": "Batch synchronization completed",
"data": {
"total": 10,
"successful": 8,
"failed": 2,
"results": [
{
"reference": "PROD001",
"success": true,
"productId": 123,
"presta_id": 456
},
{
"reference": "PROD002",
"success": false,
"error": "Invalid price value"
}
]
}
}
Code Examples
- cURL
- JavaScript
- C#
- PHP
- Python
curl -X POST https://your-domain.com/api/products/sync/batch \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"operation": "create",
"reference": "PROD001",
"name": "Coffee Machine",
"price": 1299.99,
"quantity": 50
},
{
"operation": "create",
"reference": "PROD002",
"name": "Coffee Grinder",
"price": 199.99,
"quantity": 100
}
]
}'
const syncProductsBatch = async (products) => {
const response = await fetch('https://your-domain.com/api/products/sync/batch', {
method: 'POST',
headers: {
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ products })
});
const result = await response.json();
console.log(`Batch sync: ${result.data.successful}/${result.data.total} successful`);
// Handle failures
result.data.results.forEach(item => {
if (!item.success) {
console.error(`Failed: ${item.reference} - ${item.error}`);
}
});
return result;
};
// Usage
const products = [
{ operation: 'create', reference: 'PROD001', name: 'Coffee Machine', price: 1299.99, quantity: 50 },
{ operation: 'create', reference: 'PROD002', name: 'Coffee Grinder', price: 199.99, quantity: 100 }
];
syncProductsBatch(products).catch(console.error);
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public async Task<BatchSyncResponse> SyncProductsBatchAsync(List<ProductSyncRequest> products)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/api/products/sync/batch");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
var batchRequest = new { products = products };
var jsonContent = JsonSerializer.Serialize(batchRequest);
request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<BatchSyncResponse>(responseContent);
}
// Usage
var products = new List<ProductSyncRequest>
{
new ProductSyncRequest { Operation = "create", Reference = "PROD001", Name = "Coffee Machine", Price = 1299.99m, Quantity = 50 },
new ProductSyncRequest { Operation = "create", Reference = "PROD002", Name = "Coffee Grinder", Price = 199.99m, Quantity = 100 }
};
var result = await SyncProductsBatchAsync(products);
Console.WriteLine($"Batch sync: {result.Data.Successful}/{result.Data.Total} successful");
<?php
function syncProductsBatch($products, $jwtToken) {
$ch = curl_init('https://your-domain.com/api/products/sync/batch');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $jwtToken,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['products' => $products]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Usage
$products = [
['operation' => 'create', 'reference' => 'PROD001', 'name' => 'Coffee Machine', 'price' => 1299.99, 'quantity' => 50],
['operation' => 'create', 'reference' => 'PROD002', 'name' => 'Coffee Grinder', 'price' => 199.99, 'quantity' => 100]
];
$result = syncProductsBatch($products, 'YOUR_JWT_TOKEN');
echo "Batch sync: {$result['data']['successful']}/{$result['data']['total']} successful\n";
import requests
def sync_products_batch(products, jwt_token):
url = 'https://your-domain.com/api/products/sync/batch'
headers = {
'Authorization': f'Bearer {jwt_token}',
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, json={'products': products})
return response.json()
# Usage
products = [
{'operation': 'create', 'reference': 'PROD001', 'name': 'Coffee Machine', 'price': 1299.99, 'quantity': 50},
{'operation': 'create', 'reference': 'PROD002', 'name': 'Coffee Grinder', 'price': 199.99, 'quantity': 100}
]
result = sync_products_batch(products, 'YOUR_JWT_TOKEN')
print(f"Batch sync: {result['data']['successful']}/{result['data']['total']} successful")
DELETE /api/products/sync
Deletes a product from PrestaShop by its reference.
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
reference | string | Yes | Product reference/SKU to delete |
Success Response (200 OK)
{
"success": true,
"message": "Product deleted successfully",
"data": {
"reference": "PROD001",
"presta_id": 456
}
}
Code Examples
- cURL
- JavaScript
- C#
- PHP
- Python
curl -X DELETE "https://your-domain.com/api/products/sync?reference=PROD001" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
const deleteProduct = async (reference) => {
const response = await fetch(
`https://your-domain.com/api/products/sync?reference=${encodeURIComponent(reference)}`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`
}
}
);
const result = await response.json();
if (result.success) {
console.log(`Product ${reference} deleted successfully`);
} else {
console.error(`Failed to delete: ${result.error}`);
}
return result;
};
// Usage
deleteProduct('PROD001').catch(console.error);
public async Task<DeleteResponse> DeleteProductAsync(string reference)
{
var request = new HttpRequestMessage(
HttpMethod.Delete,
$"/api/products/sync?reference={Uri.EscapeDataString(reference)}"
);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _jwtToken);
var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<DeleteResponse>(responseContent);
}
// Usage
var result = await DeleteProductAsync("PROD001");
if (result.Success)
{
Console.WriteLine($"Product deleted: {result.Data.Reference}");
}
<?php
function deleteProduct($reference, $jwtToken) {
$url = 'https://your-domain.com/api/products/sync?reference=' . urlencode($reference);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $jwtToken
]);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Usage
$result = deleteProduct('PROD001', 'YOUR_JWT_TOKEN');
if ($result['success']) {
echo "Product deleted: " . $result['data']['reference'];
}
import requests
from urllib.parse import urlencode
def delete_product(reference, jwt_token):
url = f'https://your-domain.com/api/products/sync?reference={requests.utils.quote(reference)}'
headers = {
'Authorization': f'Bearer {jwt_token}'
}
response = requests.delete(url, headers=headers)
return response.json()
# Usage
result = delete_product('PROD001', 'YOUR_JWT_TOKEN')
if result['success']:
print(f"Product deleted: {result['data']['reference']}")
GET /api/products/sync
Retrieves the status of a specific product synchronization by its sync ID.
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
syncId | string | Yes | Unique synchronization ID from sync operation |
Success Response (200 OK)
{
"success": true,
"data": {
"syncId": "sync_abc123xyz",
"reference": "PROD001",
"status": "completed",
"operation": "create",
"startedAt": "2026-01-13T10:30:00.000Z",
"completedAt": "2026-01-13T10:30:05.000Z",
"duration": 5000,
"productId": 123,
"presta_id": 456
}
}
Status Values
pending: Synchronization queued but not startedprocessing: Currently being synchronizedcompleted: Successfully completedfailed: Synchronization failedpartial: Partially completed
Code Examples
- cURL
- JavaScript
- C#
- PHP
- Python
curl -X GET "https://your-domain.com/api/products/sync?syncId=sync_abc123xyz" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
const getSyncStatus = async (syncId) => {
const response = await fetch(
`https://your-domain.com/api/products/sync?syncId=${encodeURIComponent(syncId)}`,
{
method: 'GET',
headers: {
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`
}
}
);
const result = await response.json();
if (result.success) {
console.log(`Status: ${result.data.status}`);
console.log(`Duration: ${result.data.duration}ms`);
}
return result;
};
// Usage with polling
const waitForSync = async (syncId, maxAttempts = 20) => {
for (let i = 0; i < maxAttempts; i++) {
const status = await getSyncStatus(syncId);
if (status.data.status === 'completed' || status.data.status === 'failed') {
return status;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
throw new Error('Sync timeout');
};
waitForSync('sync_abc123xyz').catch(console.error);
public async Task<SyncStatusResponse> GetSyncStatusAsync(string syncId)
{
var request = new HttpRequestMessage(
HttpMethod.Get,
$"/api/products/sync?syncId={Uri.EscapeDataString(syncId)}"
);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _jwtToken);
var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<SyncStatusResponse>(responseContent);
}
// Usage with polling
public async Task<SyncStatusResponse> WaitForSyncAsync(string syncId, int maxAttempts = 20)
{
for (int i = 0; i < maxAttempts; i++)
{
var status = await GetSyncStatusAsync(syncId);
if (status.Data.Status == "completed" || status.Data.Status == "failed")
{
return status;
}
await Task.Delay(2000);
}
throw new TimeoutException("Sync operation timed out");
}
<?php
function getSyncStatus($syncId, $jwtToken) {
$url = 'https://your-domain.com/api/products/sync?syncId=' . urlencode($syncId);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $jwtToken
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Usage with polling
function waitForSync($syncId, $jwtToken, $maxAttempts = 20) {
for ($i = 0; $i < $maxAttempts; $i++) {
$status = getSyncStatus($syncId, $jwtToken);
if ($status['data']['status'] === 'completed' || $status['data']['status'] === 'failed') {
return $status;
}
sleep(2);
}
throw new Exception('Sync timeout');
}
$result = waitForSync('sync_abc123xyz', 'YOUR_JWT_TOKEN');
echo "Sync completed with status: " . $result['data']['status'];
import requests
import time
def get_sync_status(sync_id, jwt_token):
url = f'https://your-domain.com/api/products/sync?syncId={requests.utils.quote(sync_id)}'
headers = {
'Authorization': f'Bearer {jwt_token}'
}
response = requests.get(url, headers=headers)
return response.json()
# Usage with polling
def wait_for_sync(sync_id, jwt_token, max_attempts=20):
for i in range(max_attempts):
status = get_sync_status(sync_id, jwt_token)
if status['data']['status'] in ['completed', 'failed']:
return status
time.sleep(2)
raise TimeoutError('Sync operation timed out')
result = wait_for_sync('sync_abc123xyz', 'YOUR_JWT_TOKEN')
print(f"Sync completed with status: {result['data']['status']}")
POST /api/webhooks/gesmart/products
Receives real-time product updates from GeSmart ERP via webhook notifications.
Headers
| Header | Value | Required | Description |
|---|---|---|---|
Content-Type | application/json | Yes | Request content type |
x-gesmart-signature | {hmac_signature} | Yes | HMAC SHA256 signature for verification |
Signature Verification
The webhook endpoint verifies incoming requests using HMAC SHA256 signature:
- GeSmart generates a signature using the webhook secret
- Signature is sent in
x-gesmart-signatureheader - Sync app validates the signature before processing
Configuration: Set the GESMART_WEBHOOK_SECRET environment variable.
Request Body
Same structure as the single product sync endpoint.
{
"operation": "update",
"reference": "PROD001",
"name": "Updated Product Name",
"price": 1499.99,
"quantity": 75,
"active": true
}
Success Response (200 OK)
{
"success": true,
"message": "Webhook received and processing started",
"data": {
"syncId": "sync_webhook_xyz789",
"reference": "PROD001",
"queued": true
}
}
Code Examples
- cURL
- JavaScript
- C#
- PHP
- Python
# Calculate HMAC signature
PAYLOAD='{"operation":"update","reference":"PROD001","name":"Updated Product","price":1499.99,"quantity":75}'
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary | base64)
curl -X POST https://your-domain.com/api/webhooks/gesmart/products \
-H "Content-Type: application/json" \
-H "x-gesmart-signature: $SIGNATURE" \
-d "$PAYLOAD"
const crypto = require('crypto');
const sendWebhook = async (productData, webhookSecret) => {
const payload = JSON.stringify(productData);
// Generate HMAC signature
const signature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('base64');
const response = await fetch('https://your-domain.com/api/webhooks/gesmart/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-gesmart-signature': signature
},
body: payload
});
return await response.json();
};
// Usage
const productUpdate = {
operation: 'update',
reference: 'PROD001',
name: 'Updated Product',
price: 1499.99,
quantity: 75
};
sendWebhook(productUpdate, process.env.WEBHOOK_SECRET);
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class GesmartWebhookClient
{
private readonly HttpClient _httpClient;
private readonly string _webhookSecret;
public GesmartWebhookClient(string webhookUrl, string webhookSecret)
{
_httpClient = new HttpClient { BaseAddress = new Uri(webhookUrl) };
_webhookSecret = webhookSecret;
}
private string GenerateSignature(string payload)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_webhookSecret)))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return Convert.ToBase64String(hash);
}
}
public async Task<WebhookResponse> SendProductUpdateAsync(ProductSyncRequest product)
{
var payload = JsonSerializer.Serialize(product);
var signature = GenerateSignature(payload);
var request = new HttpRequestMessage(HttpMethod.Post, "/api/webhooks/gesmart/products");
request.Headers.Add("x-gesmart-signature", signature);
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<WebhookResponse>(responseContent);
}
}
// Usage
var client = new GesmartWebhookClient("https://your-domain.com", Environment.GetEnvironmentVariable("WEBHOOK_SECRET"));
var product = new ProductSyncRequest { Operation = "update", Reference = "PROD001", Name = "Updated Product", Price = 1499.99m, Quantity = 75 };
var result = await client.SendProductUpdateAsync(product);
<?php
function sendWebhook($productData, $webhookSecret) {
$payload = json_encode($productData);
// Generate HMAC signature
$signature = base64_encode(hash_hmac('sha256', $payload, $webhookSecret, true));
$ch = curl_init('https://your-domain.com/api/webhooks/gesmart/products');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'x-gesmart-signature: ' . $signature
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Usage
$productUpdate = [
'operation' => 'update',
'reference' => 'PROD001',
'name' => 'Updated Product',
'price' => 1499.99,
'quantity' => 75
];
$result = sendWebhook($productUpdate, getenv('WEBHOOK_SECRET'));
echo "Webhook sent: " . $result['message'];
import requests
import hmac
import hashlib
import base64
import json
import os
def send_webhook(product_data, webhook_secret):
url = 'https://your-domain.com/api/webhooks/gesmart/products'
payload = json.dumps(product_data)
# Generate HMAC signature
signature = base64.b64encode(
hmac.new(
webhook_secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).digest()
).decode('utf-8')
headers = {
'Content-Type': 'application/json',
'x-gesmart-signature': signature
}
response = requests.post(url, headers=headers, data=payload)
return response.json()
# Usage
product_update = {
'operation': 'update',
'reference': 'PROD001',
'name': 'Updated Product',
'price': 1499.99,
'quantity': 75
}
result = send_webhook(product_update, os.getenv('WEBHOOK_SECRET'))
print(f"Webhook sent: {result['message']}")
📊 Data Models
Complete Product Object
interface Product {
// Required fields
operation: 'create' | 'update';
reference: string; // Max 64 chars
name: string; // 1-255 chars
price: number; // >= 0
quantity: number; // >= 0, integer
// Optional basic fields
description?: string; // Max 10000 chars
active?: boolean; // Default: true
categories?: number[]; // PrestaShop category IDs
ean13?: string; // 13 chars or empty
weight?: number; // >= 0, in kg
// Extended fields
brand?: Brand;
images?: Image[]; // Max 10 images
technical_data?: TechnicalData[]; // Max 100 items
}
Brand Model
interface Brand {
name: string; // Required
description?: string;
}
Image Model
interface Image {
fileName: string; // Required, valid extension
base64: string; // Required, max 10MB per image
}
Supported formats: jpg, jpeg, png, gif, webp
Technical Data Model
interface TechnicalData {
Gruppo: string; // Required - Group/Category
Descrizione: string; // Required - Description
Valore: string; // Required - Value
Suffisso?: string; // Optional - Unit suffix
PrintOrder?: number; // Optional - Display order (default: 0)
}
Limits: Maximum 100 technical data items per product
⚠️ Error Handling
HTTP Status Codes
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 400 | Bad Request | Invalid request data or validation failed |
| 401 | Unauthorized | Missing or invalid authentication token |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Resource conflict (duplicate reference) |
| 422 | Unprocessable Entity | Validation errors in request data |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error during processing |
| 502 | Bad Gateway | PrestaShop API communication error |
Error Response Format
{
"success": false,
"error": "Validation failed",
"details": [
{
"field": "price",
"message": "Price must be a positive number"
}
]
}
✅ Best Practices
1. Image Optimization
Recommended specs:
- Maximum dimensions: 1200x1200 pixels
- Format: JPEG for photos, PNG for graphics with transparency
- Quality: 80-85% for JPEG
- Size: Under 500KB per image (before base64 encoding)
2. Batch Size Optimization
- Optimal batch size: 50 products per request
- Add 1-2 second delay between batches to avoid rate limiting
- Monitor partial failures in batch responses
3. Validation Before Sync
- Validate data locally before sending to API
- Check required fields: reference, name, price, quantity
- Verify image count (max 10) and technical data count (max 100)
- Ensure proper data types for all fields
4. Webhook Security
Security checklist:
- ✅ Use strong webhook secret (minimum 32 characters)
- ✅ Always verify HMAC signatures
- ✅ Use HTTPS for webhook endpoint
- ✅ Log all webhook attempts for audit trail
🔍 Data Flow Diagram
sequenceDiagram
participant ERP as GeSmart ERP
participant API as Sync App API
participant DB as Database
participant PS as PrestaShop API
Note over ERP,PS: Single Product Sync Flow
ERP->>API: POST /api/products/sync
API->>API: Validate JWT Token
API->>API: Validate Product Data
API->>DB: Create Sync Record
API->>PS: Create/Update Product
PS-->>API: Product ID
opt If images provided
loop For each image
API->>PS: Upload Image
PS-->>API: Image ID
end
end
opt If brand provided
API->>PS: Create/Update Brand
PS-->>API: Brand ID
API->>PS: Link Brand to Product
end
opt If technical data provided
API->>PS: Save Technical Specifications
PS-->>API: Confirmation
end
API->>DB: Update Sync Status
API-->>ERP: Success Response
Note over ERP,PS: Webhook Flow
ERP->>API: POST /api/webhooks/gesmart/products
API->>API: Verify HMAC Signature
API->>DB: Queue Sync Job
API-->>ERP: 200 OK (Immediate Response)
Note over API,PS: Async Processing
API->>API: Process Queue
API->>PS: Sync Product
PS-->>API: Result
API->>DB: Update Sync Status
📚 Additional Resources
- API Overview - General API information
- Authentication - Authentication guide
- Sync Endpoints - General sync endpoints
💡 Support
For issues or questions about the Products Sync API:
- Check the error handling section above
- Review best practices
- Test with the Postman collection
- Contact technical support with detailed logs and sync IDs