Shopify a scala: sync, riconciliazione e operazioni catalog senza drift
- shopify
- automation
- ecommerce
Più cresci e più Shopify smette di essere “lo store” per diventare “uno dei sistemi”. Accanto allo store c’è un ERP, un PIM, un magazzino su un foglio Excel, una bolla che gira tra produzione e logistica, magari un secondo Shopify per il mercato estero. Ogni sistema vuole essere la fonte di verità per qualcosa: prezzi, scorte, ordini, status pubblicazione. Quando non sono d’accordo, qualcuno tra customer care, contabilità e logistica passa il pomeriggio a riconciliare a mano.
Questo post raccoglie i pattern che usiamo per tenere un catalogo Shopify allineato con un ERP custom su un volume di 600+ SKU, in un settore di retail B2C (luxury resale, ma il pattern vale per electronics, home & living, sporting goods). Tre temi: sync 2-way con backoff, riconciliazione periodica, bulk operations sicure.
Sync 2-way: 7 entità, una regola d’oro
Il punto di partenza è capire chi è source of truth per cosa. Nel nostro caso:
- Prodotti: ERP è truth (perché c’è la logica di pricing, sourcing, conto vendita), Shopify riceve.
- Inventario: ERP è truth, ma Shopify ha “delta” da ordini live → reconciliation periodica.
- Customers: Shopify è truth per chi arriva via storefront, ERP per B2B.
- Orders: Shopify è truth (è il sistema dove l’ordine nasce), ERP riceve.
- Transactions / payouts / balance: Shopify Payments è truth, ERP riceve per contabilità.
Sette edge functions girano ogni 15 minuti, ciascuna su una singola entità. Pattern comune:
- Cursor-based pull: tracciamo l’ultimo
updated_atprocessato per evitare full scan inutili. - Rate limit awareness: backoff esponenziale sulle 429 Shopify, max 3 retry, dead-letter su esaurimento.
- Idempotency key: ogni record processato ha un hash; se rientra identico, skip.
- Webhook integration: in parallelo al polling, un handler webhook ingerisce eventi real-time per
orders/create,inventory_levels/update,products/update.
Webhook + polling sembra ridondante, ma è il pattern che ha dato più affidabilità. I webhook a volte si perdono (manutenzioni Shopify, network blip, retry esauriti); il poll riempie i buchi senza intervento.
Webhook handler con register dinamico
Una nota tecnica che fa la differenza nel medio periodo: il register dei webhook lo gestisce un’edge function dedicata, non l’admin UI. Ogni volta che cambiamo dominio (test → staging → prod) o aggiungiamo un topic, lo script register-webhooks riallinea la configurazione Shopify con la lista che vive in codice. Niente più “il webhook in prod puntava ancora al vecchio dominio”.
Lato handler, ogni webhook viene verificato (HMAC), persistito in una tabella webhook_inbox, e processato da un worker separato. Questo separa il “received in time” (importante per Shopify) dal “processed correctly” (che può fare retry).
Riconciliazione mensile: il netting silenzioso
Anche con sync ogni 15 minuti, dopo 30 giorni c’è drift. Le ragioni sono varie: ordini cancellati dopo il sync, inventory rettifiche manuali fatte in Shopify, prodotti unpubblicati e ripubblicati con SKU diverso. La riconciliazione mensile è il momento in cui pareggiamo i conti.
Lo script gira la prima notte del mese e fa tre cose:
- Inventory reconciliation: per ogni SKU, confronta quantity Shopify vs ERP. Se diverso > soglia (5 unità o 10%), genera un log da review. Se diverso ma ricostruibile da ordini noti, applica fix automatico.
- Status reconciliation: per ogni listing, controlla
published_at,status,publication scope. I drift comuni (prodotto attivo in ERP ma archived in Shopify) finiscono in una coda di review. - Refund/payout backfill: scansiona gli ordini dell’ultimo mese e verifica che payout e refund siano correttamente associati. I 100%-discount (gift card, comp) hanno un handler dedicato per il matching con i payout Shopify.
Esempio concreto: in un mese tipo su 600 SKU il job rileva drift su 15-20 listing, di cui 12-15 auto-fixed e 3-5 in manual review. Il tempo per chiudere la review è circa 30 minuti, contro le 4-6 ore che ci voleva fare lo stesso check manuale.
Bulk operations: compare-at, merge prodotti, push catalog
Le operazioni one-shot sul catalogo sono il momento più rischioso. Cambiare il compareAtPrice su 600 SKU con un upload CSV manuale è il modo migliore per ritrovarsi con 30 prodotti sbagliati e nessun log di cosa è stato fatto.
Pattern per bulk operations:
- Dry-run di default: lo script gira sempre prima in modalità preview. Output: lista di righe che cambieranno, con valori before/after.
- Conferma esplicita: solo dopo la review umana del CSV diff, lo script esegue.
- Batch + delay: max 50 update per batch, delay 200ms tra batch. Su 600 SKU significa 4-5 minuti totali, dentro i rate limit Shopify.
- Backfill log: ogni modifica viene scritta su una tabella
bulk_ops_logconentity_id,field,before,after,actor,timestamp. Tracciabilità completa.
Il merge prodotti è un caso particolare. Quando un catalogo cresce per import multipli (foto, schede, fornitori diversi), nascono duplicati. Lo script di merge identifica i candidati (match su titolo + vendor + SKU pattern), li mette in una coda, e applica il merge dopo conferma. Il prodotto “winner” eredita varianti, immagini e metafield; il “loser” viene archiviato con redirect 301 dalla URL handle vecchia. Zero link rotti, niente perdita di SEO equity.
Markets e publications: il sync per-canale
Per chi vende su più mercati (Shopify Markets) o canali (storefront + Shop App + B2B), il problema “il prodotto è pubblicato dove?” diventa serio. La nostra Markets/Publications pull è un’edge function che, per ogni prodotto, interroga le publication API e scrive una matrice product × market × status su database. Da lì la UI interna mostra: questo prodotto è live su IT, draft su UK, hidden su B2B.
È il tipo di vista che Shopify Admin non offre nativamente in modo aggregato, ma che ai planner e ai marketing serve quotidianamente per decidere dove spingere.
Pattern replicabile
Se gestisci un catalogo Shopify > 200 SKU con sync verso un ERP o un altro sistema:
- Mappa source of truth per ogni entità prima di scrivere codice. Disaccordo qui = drift garantito.
- Combina webhook real-time e polling cursor-based. Uno solo non basta, sempre.
- HMAC + inbox table + worker separato per webhook reliability.
- Riconciliazione mensile con soglie: auto-fix sotto soglia, manual review sopra.
- Bulk ops sempre con dry-run + log. Senza, prima o poi si rompe qualcosa.
- Markets/Publications su database, non da query live.
L’investimento iniziale per impostare questo schema è 2-4 settimane su un catalogo medio. Il vantaggio non è “risparmiare X ore alla settimana”: è eliminare la classe di errori in cui ti accorgi del drift solo dopo che il cliente si è lamentato. Per un ecommerce a 600+ SKU, su volumi seri, è la differenza tra crescere senza paura e dover congelare il catalogo ogni volta che apri un nuovo canale.