Guida al Batch Processing e Callback - Skuno API
📋 Panoramica
Il sistema di batch processing di Skuno permette di inviare più prodotti contemporaneamente per l'arricchimento AI, ricevendo callback individuali per ogni prodotto completato. Questa guida fornisce tutti i dettagli tecnici per implementare correttamente l'integrazione.
🔑 Autenticazione
Skuno utilizza l'autenticazione tramite API Key per tutte le integrazioni.
POST /functions/v1/products-batch
Content-Type: application/json
apikey: your_api_key_here
L'API Key garantisce sicurezza e semplicità d'uso per tutte le operazioni di batch processing.
🚀 Endpoint Batch Processing
Invio Batch di Prodotti
Endpoint: POST /functions/v1/products-batch
Request Headers
Content-Type: application/json
apikey: your_api_key_here
⚠️ Aggiornamento Importante: Campo Language Obbligatorio
A partire da questa versione, ogni prodotto deve includere il campo language nello schema_override. Questo campo specifica la lingua in cui l'AI deve arricchire il prodotto.
Lingue supportate:
en- Inglesefr- Franceseit- Italianoes- Spagnolo
?> Nota: se il campo language non viene specificato o contiene un valore non supportato, la richiesta verrà rifiutata con un errore di validazione.
🖼️ Gestione Immagini
Richiesta Immagini
Per richiedere anche l'arricchimento automatico delle immagini durante il processo di batch, aggiungi require_images: true all'interno dello schema_override di ogni prodotto:
{
"barcode": "8001234567890",
"brand": "Nike",
"schema_override": {
"type": "object",
"properties": {
"language": "it",
"require_images": true,
"product_name": {"type": "string"}
},
"required": ["language", "product_name"]
}
}
Callback con Immagini
Quando l'arricchimento (inclusa la ricerca immagini) è completato, riceverai la callback con il campo images popolato:
{
"event": "product.enriched",
"product": {
"barcode": "8001234567890",
"enriched_description": "...",
"images": [
{
"image_url": "https://example.com/image.jpg",
"thumbnail_url": "https://example.com/thumb.jpg",
"title": "Titolo Immagine",
"is_primary": true
}
],
"image_status": "completed",
"images_found": 1
}
}
Esempio di Request Body Completo
{
"callback_url": "https://your-domain.com/webhook/skuno",
"products": [
{
"barcode": "8001234567890",
"brand": "Nike",
"current_description": "Scarpe da running Air Max",
"schema_override": {
"type": "object",
"properties": {
"language": "it",
"product_name": {"type": "string"},
"description": {"type": "string"},
"brand": {"type": "string"}
},
"required": ["product_name", "brand"]
}
},
{
"barcode": "8001234567891",
"brand": "Adidas",
"current_description": "Maglietta sportiva Climacool",
"schema_override": {
"type": "object",
"properties": {
"language": {
"type": "string",
"enum": ["en", "fr", "it", "es"],
"description": "Lingua per l'arricchimento del prodotto"
},
"product_name": {"type": "string"},
"description": {"type": "string"},
"brand": {"type": "string"}
},
"required": ["product_name", "brand"]
}
},
{
"barcode": "8001234567892",
"brand": "Puma",
"current_description": "Pantaloni da allenamento",
"schema_override": {
"type": "object",
"properties": {
"language": {
"type": "string",
"enum": ["en", "fr", "it", "es"],
"description": "Lingua per l'arricchimento del prodotto"
},
"product_name": {"type": "string"},
"description": {"type": "string"},
"brand": {"type": "string"}
},
"required": ["product_name", "brand"]
}
}
]
}
Parametri
| Campo | Tipo | Obbligatorio | Descrizione |
|------|------|--------------|-------------|
| callback_url | string | Sì | URL dove ricevere le callback di completamento |
| products | array | Sì | Array di prodotti da processare |
| products[].barcode | string | Sì | Codice a barre univoco del prodotto |
| products[].brand | string | Sì | Marca del prodotto |
| products[].current_description | string | Sì | Descrizione attuale del prodotto |
| products[].schema_override | object | Sì | Schema personalizzato per l'arricchimento AI |
| products[].schema_override.properties.language | string | Sì | Lingua per l'arricchimento (en, fr, it, es) |
Response di Successo (201)
{
"success": true,
"batch_id": "b324448f-e99b-44c5-a9c6-566f880e1aa1",
"total_products": 3,
"created": 3,
"failed": 0,
"errors": [],
"message": "Batch processing completed. Created: 3, Failed: 0"
}
Response di Errore (400/401/500)
{
"success": false,
"error": "Authentication failed",
"message": "Missing authorization header"
}
📡 Sistema di Callback
Comportamento delle Callback
- IMPORTANTE: il sistema invia una callback per ogni prodotto processato, non una callback unica per l'intero batch.
- 3 prodotti inviati = 3 callback ricevute
- 10 prodotti inviati = 10 callback ricevute
- Ogni callback viene inviata quando l'arricchimento AI di un singolo prodotto è completato.
Formato Callback
Ogni callback è una richiesta HTTP POST al vostro callback_url:
POST https://your-domain.com/webhook/skuno
Content-Type: application/json
User-Agent: Skuno-Webhook/1.0
{
"event": "product.enriched",
"batch_id": "b324448f-e99b-44c5-a9c6-566f880e1aa1",
"product_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-01-25T14:30:00.000Z",
"product": {
"barcode": "8001234567890",
"brand": "Nike",
"original_description": "Scarpe da running Air Max",
"enriched_description": "Scarpe da running Nike Air Max con tecnologia di ammortizzazione avanzata, ideali per corridori che cercano comfort e performance. Suola in gomma resistente e tomaia traspirante.",
"enriched_data": {
"tags": ["running", "sport", "scarpe", "nike", "air-max"],
"category": "Calzature Sportive",
"features": [
"Ammortizzazione Air Max",
"Tomaia traspirante",
"Suola in gomma resistente"
],
"target_audience": "Corridori e appassionati di fitness",
"confidence_score": 0.95
},
"images": [
{
"image_url": "https://example.com/image1.jpg",
"thumbnail_url": "https://example.com/thumb1.jpg",
"title": "Nike Air Max Lato",
"source_url": "https://source.com/page",
"width": 800,
"height": 600,
"is_primary": true,
"relevance_score": 8.5,
"quality_score": 9.0
}
],
"image_status": "completed",
"images_found": 1
},
"processing_info": {
"started_at": "2025-01-25T14:29:45.000Z",
"completed_at": "2025-01-25T14:30:00.000Z",
"processing_time_ms": 15000
}
}
Campi della Callback
| Campo | Tipo | Descrizione |
|------|------|-------------|
| event | string | Sempre product.enriched |
| batch_id | string | ID del batch originale |
| product_id | string | ID univoco del prodotto |
| timestamp | string | Timestamp ISO 8601 del completamento |
| product.barcode | string | Codice a barre originale |
| product.brand | string | Marca originale |
| product.original_description | string | Descrizione originale |
| product.enriched_description | string | Descrizione arricchita dall'AI |
| product.enriched_data | object | Dati aggiuntivi generati dall'AI |
| product.images | array | Array di oggetti contenenti le URL e metadata delle immagini del prodotto (se richieste) |
| product.image_status | string | Stato della ricerca immagini (completed, failed, no_images_found) |
| product.images_found | number | Numero totale di immagini trovate |
| processing_info | object | Informazioni sui tempi di elaborazione |
💻 Implementazione Endpoint Callback
Esempio Node.js/Express
const express = require('express');
const app = express();
app.use(express.json());
// Endpoint per ricevere le callback di Skuno
app.post('/webhook/skuno', (req, res) => {
try {
const callback = req.body;
// Validazione base
if (callback.event !== 'product.enriched') {
return res.status(400).json({ error: 'Unknown event type' });
}
// Log della callback ricevuta
console.log(`Prodotto arricchito ricevuto:`);
console.log(`- Batch ID: ${callback.batch_id}`);
console.log(`- Product ID: ${callback.product_id}`);
console.log(`- Barcode: ${callback.product.barcode}`);
console.log(`- Brand: ${callback.product.brand}`);
// Processa il prodotto arricchito
processEnrichedProduct(callback);
// Risposta di successo (IMPORTANTE!)
res.status(200).json({
status: 'received',
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Errore processing callback:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
function processEnrichedProduct(callback) {
// Salva nel database, aggiorna catalogo, invia notifiche, ecc.
const { product, batch_id } = callback;
updateProductInDatabase({
barcode: product.barcode,
enriched_description: product.enriched_description,
tags: product.enriched_data.tags,
category: product.enriched_data.category,
images: product.images,
batch_id: batch_id
});
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
Esempio PHP
<?php
// webhook.php
header('Content-Type: application/json');
$input = file_get_contents('php://input');
$callback = json_decode($input, true);
if (!$callback) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}
if ($callback['event'] !== 'product.enriched') {
http_response_code(400);
echo json_encode(['error' => 'Unknown event type']);
exit;
}
try {
error_log("Skuno callback ricevuta: " . $input);
$product = $callback['product'];
$batchId = $callback['batch_id'];
updateProduct([
'barcode' => $product['barcode'],
'enriched_description' => $product['enriched_description'],
'tags' => json_encode($product['enriched_data']['tags']),
'batch_id' => $batchId
]);
http_response_code(200);
echo json_encode([
'status' => 'received',
'timestamp' => date('c')
]);
} catch (Exception $e) {
error_log("Errore processing callback: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Internal server error']);
}
?>
Esempio Python/Flask
from flask import Flask, request, jsonify
from datetime import datetime
import logging
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
@app.route('/webhook/skuno', methods=['POST'])
def skuno_webhook():
try:
callback = request.get_json()
if not callback or callback.get('event') != 'product.enriched':
return jsonify({'error': 'Invalid event type'}), 400
app.logger.info(f"Callback ricevuta per batch {callback['batch_id']}")
app.logger.info(f"Prodotto: {callback['product']['barcode']}")
process_enriched_product(callback)
return jsonify({
'status': 'received',
'timestamp': datetime.utcnow().isoformat() + 'Z'
}), 200
except Exception as e:
app.logger.error(f"Errore processing callback: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
🔒 Sicurezza e Best Practices
1) Validazione delle Callback
function validateCallback(callback) {
const required = ['event', 'batch_id', 'product_id', 'product'];
for (const field of required) {
if (!callback[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
if (callback.event !== 'product.enriched') {
throw new Error('Invalid event type');
}
return true;
}
2) Gestione degli Errori
app.post('/webhook/skuno', (req, res) => {
try {
validateCallback(req.body);
processCallback(req.body);
res.status(200).json({ status: 'received' });
} catch (error) {
console.error('Callback error:', error);
res.status(400).json({ error: error.message });
}
});
3) Idempotenza
const processedCallbacks = new Set();
function processCallback(callback) {
const callbackId = `${callback.batch_id}-${callback.product_id}`;
if (processedCallbacks.has(callbackId)) return;
updateProduct(callback.product);
processedCallbacks.add(callbackId);
}
4) Timeout e Retry
- Skuno ritenta le callback fallite fino a 3 volte
- Timeout di 30 secondi per ogni tentativo
- Intervalli di retry: 1min, 5min, 15min
📊 Monitoraggio e Tracking
const activeBatches = new Map();
function onBatchCompleted(batchId, batch) {
const duration = new Date() - batch.started_at;
console.log(`Batch ${batchId} completato in ${duration}ms`);
}
🧪 Test e Sviluppo
Test con LocalTunnel
npm install -g localtunnel
node your-webhook-server.js
lt --port 3000
Usa l'URL fornito come callback_url (es: https://abc123.loca.lt).
Esempio di Test
const fetch = require('node-fetch');
async function testBatch() { /* ... come da guida ... */ }
testBatch();
❓ FAQ
- Quante callback riceverò per un batch di N prodotti? N callback, una per prodotto.
- Le callback arrivano in ordine? No, l'elaborazione è parallela.
- Cosa succede se il mio endpoint è offline? Retry fino a 3 volte (1m, 5m, 15m).
- Posso avere callback al completamento del batch? Non ancora; traccia le callback ricevute lato client.
- Come gestisco callback duplicate? Usa
product_ide logica di idempotenza. - Timeout per le callback? 30 secondi per ogni tentativo.
