Il retail multicanale è la norma per qualsiasi brand e-commerce con scala significativa. Un brand vende sul proprio negozio Shopify, su Amazon, su TikTok Shop e — sempre più spesso — su marketplace regionali come Allegro o Cdiscount. Ogni canale ha la propria API, il proprio modello di dati, i propri rate limit e i propri failure mode. Senza un'architettura di integrazione coerente, il risultato è una rete fragile di webhook, esportazioni manuali e discrepanze di inventario che consumano banda operativa e danneggiano l'esperienza cliente.
Questo articolo descrive l'architettura che abbiamo costruito per clienti che gestiscono tre o più canali marketplace, i pattern API che ciascuna piattaforma utilizza e i principi di sincronizzazione dei dati che prevengono i fallimenti di integrazione più comuni.
Il problema centrale: N modelli API, 1 modello di business
Ogni marketplace ha un modello di dati internamente coerente ma esternamente incompatibile. Shopify ha varianti e opzioni; Amazon ha ASIN e SKU in una gerarchia genitore-figlio; TikTok Shop ha SPU e SKU con una struttura di attributi di prodotto differente. Un ordine su Shopify ha line item; un ordine Amazon ha order item con riferimenti ASIN; gli ordini TikTok hanno order line con tracking delle promozioni. Mappare tra questi modelli senza una rappresentazione interna canonica crea un incubo di manutenzione man mano che ciascuna piattaforma evolve.
Architettura di integrazione
Integrazione Shopify
Le API REST e GraphQL Admin di Shopify sono tra le più mature nell'e-commerce. Per le nuove integrazioni usiamo l'API GraphQL — offre operazioni batch, selezione precisa dei campi ed è dove Shopify sta investendo le nuove funzionalità.
Sincronizzazione inventario tramite GraphQL
const MUTATION_AGGIORNA_INVENTARIO = gql`
mutation inventorySetOnHandQuantities($input: InventorySetOnHandQuantitiesInput!) {
inventorySetOnHandQuantities(input: $input) {
userErrors { field message }
inventoryAdjustmentGroup {
changes {
item { inventoryItemId }
delta
quantityAfterChange
}
}
}
}
`;
async function sincronizzaInventarioShopify(
articoli: { shopifyInventoryItemId: string; locationId: string; quantita: number }[]
): Promise<void> {
// Shopify permette fino a 250 articoli per mutation
const batch = chunk(articoli, 250);
for (const gruppo of batch) {
const { data } = await shopifyClient.mutate({
mutation: MUTATION_AGGIORNA_INVENTARIO,
variables: {
input: {
reason: "correction",
setQuantities: gruppo.map((articolo) => ({
inventoryItemId: articolo.shopifyInventoryItemId,
locationId: articolo.locationId,
quantity: articolo.quantita,
})),
},
},
});
if (data.inventorySetOnHandQuantities.userErrors.length > 0) {
throw new ShopifyInventorySyncError(data.inventorySetOnHandQuantities.userErrors);
}
}
}Gestione affidabile dei webhook Shopify
I webhook di Shopify non garantiscono la consegna esattamente una volta. Possono essere consegnati più volte (retry in caso di errore HTTP) e possono arrivare fuori ordine. Il tuo gestore di webhook deve essere idempotente e dovrebbe controllare i timestamp degli eventi prima di aggiornare lo stato.
@Post("webhooks/shopify")
async gestisciWebhookShopify(
@Headers("x-shopify-topic") topic: string,
@Headers("x-shopify-hmac-sha256") firma: string,
@RawBody() rawBody: Buffer,
@Body() payload: unknown,
): Promise<void> {
// 1. Verifica la firma HMAC
const firmaAttesa = createHmac("sha256", process.env.SHOPIFY_WEBHOOK_SECRET)
.update(rawBody)
.digest("base64");
if (!timingSafeEqual(Buffer.from(firma, "base64"), Buffer.from(firmaAttesa, "base64"))) {
throw new UnauthorizedException("Firma webhook non valida");
}
// 2. Accoda per elaborazione asincrona — restituisce 200 velocemente per evitare retry Shopify
await this.webhookQueue.add(topic, { topic, payload }, {
attempts: 3,
backoff: { type: "exponential", delay: 5000 },
removeOnComplete: 1000,
});
}Integrazione Amazon SP-API
L'Amazon Selling Partner API (SP-API) è il successore di MWS e opera su OAuth 2.0 con token LWA (Login with Amazon). È potente ma ha rate limit rigidi (il throttling è per operazione, per marketplace) e richiede una gestione attenta del backoff.
Client API con rate limiting
class AmazonSPAPIClient {
private readonly rateLimiter: RateLimiter;
private accessToken: string | null = null;
private scadenzaToken: Date | null = null;
constructor(private readonly credenziali: SPAPICredenziali) {
// Rate limiter per operazione basati sulle quote pubblicate da Amazon
this.rateLimiter = new RateLimiter({
"getOrders": { tokensPerSecond: 0.0167 }, // 1 richiesta al minuto
"getInventorySummaries": { tokensPerSecond: 2 },
"updateListingsItem": { tokensPerSecond: 5 },
});
}
async richiesta<T>(operazione: string, params: Record<string, unknown>): Promise<T> {
await this.rateLimiter.consuma(operazione);
const token = await this.getAccessToken();
try {
const risposta = await fetch(`${SPAPI_BASE_URL}/${operazione}`, {
headers: {
"x-amz-access-token": token,
"Content-Type": "application/json",
},
...params,
});
if (risposta.status === 429) {
const retryAfter = parseInt(risposta.headers.get("retry-after") ?? "60");
await attendi(retryAfter * 1000);
return this.richiesta(operazione, params); // retry una volta
}
return risposta.json() as T;
} catch (errore) {
throw new SPAPIError(operazione, errore);
}
}
}Elaborazione del feed ordini
Amazon non invia webhook per gli ordini — è necessario interrogare il polling sull'endpoint getOrders. Eseguiamo un job pianificato ogni 5 minuti che recupera gli ordini in stato PendingAvailability, Pending, Unshipped dall'ultimo timestamp di polling riuscito. Questo timestamp viene memorizzato in modo persistente e avanzato solo dopo l'elaborazione riuscita di tutti gli ordini recuperati.
Integrazione TikTok Shop
TikTok Shop è il canale principale più recente e la sua API è ancora in maturazione. L'architettura è simile a Shopify (REST-ish, webhook-driven) ma il modello prodotto differisce significativamente — TikTok usa il concetto di SPU (Standard Product Unit) a livello di listing e SKU a livello di variante, con attributi di compliance obbligatori che variano per categoria.
La problematica più critica specifica di TikTok è l'integrazione del live commerce. Quando un prodotto è in primo piano in una diretta LIVE, l'inventario può passare da 1.000 a zero in sessanta secondi. La sincronizzazione dell'inventario deve dare priorità agli eventi di prenotazione di TikTok Shop con latenza quasi in tempo reale, altrimenti si verificherà l'overselling. Risolviamo questo con un consumer di eventi TikTok dedicato che aggira la coda di sincronizzazione standard e blocca direttamente l'inventario nel canonical store.
Sincronizzazione inventario: la parte difficile
La sincronizzazione dell'inventario è la parte tecnicamente più impegnativa dell'integrazione multicanale. La sfida fondamentale è che più canali possono consumare simultaneamente lo stesso inventario fisico. Senza un sistema di prenotazioni, l'overselling è inevitabile.
@Injectable()
export class InventarioPrenotazioneService {
// Prenotazione atomica usando Redis per prevenire race condition
async prenotaInventario(
sku: string,
quantita: number,
canale: Canale,
idOrdine: string,
): Promise<boolean> {
const chiave = `inventario:${sku}`;
const chiavePrenotazione = `prenotazione:${idOrdine}`;
// Script Lua garantisce l'atomicità — legge e scrive in un'unica operazione
const luaScript = `
local corrente = tonumber(redis.call('GET', KEYS[1]) or '0')
if corrente < tonumber(ARGV[1]) then
return 0 -- scorte insufficienti
end
redis.call('DECRBY', KEYS[1], ARGV[1])
redis.call('SETEX', KEYS[2], 3600, ARGV[2]) -- prenotazione scade in 1h
return 1 -- successo
`;
const risultato = await redis.eval(
luaScript,
2,
chiave,
chiavePrenotazione,
quantita.toString(),
JSON.stringify({ sku, quantita, canale, idOrdine }),
);
return risultato === 1;
}
}Sincronizzazione del catalogo prodotti
Mantenere un catalogo prodotti canonico e distribuirlo su più canali è complesso perché ogni canale richiede dati diversi: Amazon ha bisogno di bullet point, search term e browse node ID; Shopify ha bisogno di metafield e assegnazioni alle collezioni; TikTok ha bisogno di certificati di compliance e attributi specifici per categoria. Modelliamo questo come un prodotto canonico con override per canale, memorizzati come JSONB in PostgreSQL.
Gestione degli errori e riconciliazione
- Tutte le operazioni di sincronizzazione scrivono in un audit log immutabile con input, output, timestamp e identificatore del canale.
- Le operazioni di sincronizzazione fallite vengono ritentate con backoff esponenziale e spostate in una coda dead-letter dopo N tentativi.
- Un job di riconciliazione giornaliero confronta i livelli di inventario canonici con i livelli riportati da ciascuna API marketplace e segnala le discrepanze.
- Alert inviati ai team operativi quando la discrepanza di inventario supera il 5% per qualsiasi SKU.
- Ogni risposta API del marketplace include correlation ID memorizzati insieme alla voce dell'audit log per il debugging.
Conclusioni
L'integrazione unified commerce non è mai solo un esercizio di integrazione API — è un problema di consistenza dei dati su larga scala. I team che lo fanno bene investono in un modello di dati canonico fin dal primo giorno, trattano la prenotazione dell'inventario come un sistema critico in tempo reale e costruiscono audit e riconciliazione nell'architettura prima di scrivere la prima funzione di sincronizzazione. I team che lo fanno male finiscono con una patchwork di chiamate API dirette, fogli di correzione manuali e un team di assistenza clienti sopraffatto da dispute per overselling.
Nexora ha costruito integrazioni unified commerce che collegano Shopify, Amazon SP-API, TikTok Shop, Miravia ed ERP interni per clienti retail. Se stai costruendo o scalando un'operazione commerce multicanale, possiamo progettare e implementare il layer di integrazione.
Tag
Prossimo passo
Hai bisogno di implementare questa architettura?
Il nostro team di ingegneria costruisce e scala i sistemi descritti in questo articolo. Dalla discovery alla produzione — con risultati misurabili.