The five failure modes below come up again and again. Design against each of them.
First, decide who is the boss of each piece of data
Before writing a line of code, answer this for every data type (customers, products, prices, stock, orders, invoices):
Which system is the source of truth?
If both systems can edit the same field, you will have conflicts. The cleanest design gives each field one owner:
- Products and prices: owned by the ERP (see also Odoo or custom software? How to decide)
- Orders: created in the shop, then owned by the ERP once imported
- Customer addresses: owned by whichever system the customer edits, with a clear rule
A two-way sync is possible, but it is much harder. Prefer one-way flows wherever you can.
Failure 1: Duplicates
What happens: a job runs twice (a retry, a timeout, a double click, a webhook delivered twice) and creates two orders, two invoices or two customers.
Why: the receiving system cannot tell that it has already processed this event.
How to design against it:
- Idempotency keys: give each operation a unique, stable identifier (such as the shop's order number). Before creating anything, check whether it already exists. Better, make the target reject duplicates.
- Store the external ID of each record on both sides, so you always know what corresponds to what.
- Use upserts (create or update) instead of blind creates.
- Treat webhooks as at-least-once delivery: assume every event can arrive twice, or out of order.
For business readers, this is the reason you once got two invoices for one order. At higher volume, the same logic belongs in your own scripts rather than a no-code tool billed per run. That trade-off is covered in Automating without per-task pricing: scripts vs no-code.
Failure 2: Lost updates
What happens: a change in one system never reaches the other. A webhook was dropped while your server was down, an API returned an error and nobody retried, or a job crashed halfway.
How to design against it:
- Retries with backoff for temporary errors
- A queue or a log of pending changes, so nothing exists only in memory
- Reconciliation jobs: a periodic comparison (nightly, for example) of both systems that finds and fixes differences. Webhooks give speed, reconciliation gives correctness. You want both.
- Dead-letter handling: items that fail repeatedly go to a place where a human can see them, instead of vanishing
- Alerts when the queue grows or a job has not run
Need a sync that survives retries?
We design shop and ERP integrations with source-of-truth rules, queues, and reconciliation, not a fragile one-shot webhook.
Failure 3: Conflicting updates
What happens: both systems change the same record close together. One overwrites the other, and someone's work disappears.
How to design against it:
- One owner per field, as described above
- If two-way sync is needed, use timestamps or version numbers and a clear rule: last write wins, source system wins, or conflicts go to a human
- Avoid syncing derived fields (totals, statuses) that each system computes differently
- Beware sync loops: an update in A triggers an update in B, which triggers an update in A. Break the loop by recording the origin of each change and ignoring changes you made yourself
Failure 4: Mismatched data models
What happens: the two systems do not describe the world the same way. One has "variants," the other has separate products. One stores a full name, the other first and last names. One has taxes included in prices, the other adds them. One counts "available" stock, the other counts "on hand."
How to design against it:
- Write a mapping document field by field, with the transformation rules and examples
- Handle the awkward cases explicitly: products with variants, bundles, partial refunds, discounts, multiple shipping addresses, tax rules by country
- Normalise formats: dates and time zones, currencies and decimal handling (store money as integers or exact decimals, never floating point), phone and country formats, character encoding
- Validate on the way in, and reject or flag records that do not fit rather than guessing
- Test with real, ugly data: the customer with an apostrophe in their name, the order with 40 lines, the product without a SKU
Stock deserves special mention. "Available stock" depends on reservations, pending orders, returns and warehouses. Decide exactly what number you sync, and when.
Failure 5: Silent failure
What happens: the sync stops working, or works wrongly, and nobody notices for days or weeks. By the time someone sees it, there are hundreds of inconsistencies.
How to design against it:
- Monitor the absence of success, not only errors: "no orders imported in 3 hours during business hours" should raise an alert
- Track counts: records sent vs records received, per day
- Log each run with a summary: processed, created, updated, skipped, failed
- A visible dashboard or daily digest showing health at a glance
- A manual re-sync tool for a single record, so support can fix cases without a developer
- Expiring credentials: API tokens and OAuth connections expire. Track expiry dates and alert before they lapse
Webhooks, polling, or both?
- Webhooks: the source tells you when something changes. Fast and efficient, but they can be lost or duplicated, and your endpoint must be available and secure (verify signatures).
- Polling: you ask regularly for changes since your last check. Simpler to reason about, a little slower, and it uses API quota.
- Both: webhooks for speed, a polling or reconciliation job as the safety net. This is the robust option for anything important.
Respect API rate limits: use batching where available, back off when you receive "too many requests," and avoid fetching everything on every run. Use "changed since" filters or cursors.
A minimal robust design
- Events from the source go into a queue (or table) first, never processed inline in the webhook handler.
- A worker processes events, idempotently, with retries.
- Failures after N attempts go to a review list with alerts.
- A nightly reconciliation compares both systems and repairs differences.
- Every record stores its external ID and last sync time.
- A dashboard or digest shows counts, errors and lag.
- Secrets, tokens and keys stay in your vault, with expiry tracking.
Common pitfalls
- Two-way sync everywhere "just in case"
- No external IDs stored, so matching relies on names or emails
- Trusting webhooks as the only mechanism
- Processing the webhook inside the request, then timing out
- Floating point for money
- Time zone confusion causing off-by-one-day errors
- No alerting on silence
- Full re-sync every run, hitting rate limits
- Hardcoding edge-case rules in several places
Checklist
- Source of truth defined for each data type
- Mapping document written, edge cases listed
- External IDs stored on both sides
- Operations are idempotent
- Retries with backoff, and a review list for repeated failures
- Reconciliation job in place
- Conflict rules defined for any two-way field
- Alerts on errors, silence and expiring credentials
- Logs and a health summary available
- Manual fix tool for single records
From fragile sync to a design you can operate
If your shop and back office already disagree, or you are about to connect them, write us before the drift becomes a weekly firefight.
Les cinq modes de panne ci-dessous reviennent sans cesse. Concevez contre chacun.
D'abord, décider qui est le chef de chaque donnée
Avant d'écrire une ligne de code, répondez pour chaque type de donnée (clients, produits, prix, stock, commandes, factures):
Quel système est la source de vérité ?
Si les deux systèmes peuvent éditer le même champ, vous aurez des conflits. Le design le plus propre donne à chaque champ un seul propriétaire:
- Produits et prix: portés par l'ERP (voir aussi Odoo ou logiciel sur mesure ? Comment décider)
- Commandes: créées dans la boutique, puis portées par l'ERP une fois importées
- Adresses clients: portées par le système où le client les modifie, avec une règle claire
Une sync bidirectionnelle est possible, mais bien plus difficile. Préférez des flux à sens unique partout où vous le pouvez.
Panne 1: Les doublons
Ce qui se passe: un job tourne deux fois (un retry, un timeout, un double clic, un webhook livré deux fois) et crée deux commandes, deux factures ou deux clients.
Pourquoi: le système qui reçoit ne peut pas savoir qu'il a déjà traité cet événement.
Comment concevoir contre:
- Clés d'idempotence: donner à chaque opération un identifiant unique et stable (par exemple le numéro de commande de la boutique). Avant de créer quoi que ce soit, vérifier si ça existe déjà. Mieux: faire rejeter les doublons par la cible.
- Stocker l'ID externe de chaque enregistrement des deux côtés, pour toujours savoir ce qui correspond à quoi.
- Utiliser des upserts (créer ou mettre à jour) plutôt que des créations à l'aveugle.
- Traiter les webhooks comme une livraison au moins une fois: supposez que chaque événement peut arriver deux fois, ou dans le désordre.
Pour un lecteur métier, c'est la raison pour laquelle vous avez un jour reçu deux factures pour une commande. À plus fort volume, la même logique appartient à vos propres scripts plutôt qu'à un outil no-code facturé à l'exécution. Ce compromis est traité dans Automatiser sans facturation à la tâche: scripts ou no-code.
Panne 2: Les mises à jour perdues
Ce qui se passe: un changement dans un système n'atteint jamais l'autre. Un webhook a été perdu pendant que votre serveur était down, une API a renvoyé une erreur sans retry, ou un job a planté à mi-chemin.
Comment concevoir contre:
- Retries avec backoff pour les erreurs temporaires
- Une file ou un journal des changements en attente, pour que rien n'existe seulement en mémoire
- Jobs de réconciliation: une comparaison périodique (chaque nuit, par exemple) des deux systèmes qui trouve et corrige les écarts. Les webhooks donnent la vitesse, la réconciliation donne la correction. Vous voulez les deux.
- Dead-letter: les éléments qui échouent encore et encore vont dans un endroit où un humain les voit, au lieu de disparaître
- Alertes quand la file grossit ou qu'un job n'a pas tourné
Une sync qui survit aux retries ?
On conçoit des intégrations boutique / ERP avec sources de vérité, files et réconciliation, pas un webhook fragile en one-shot.
Panne 3: Les mises à jour conflictuelles
Ce qui se passe: les deux systèmes changent le même enregistrement presque en même temps. L'un écrase l'autre, et le travail de quelqu'un disparaît.
Comment concevoir contre:
- Un propriétaire par champ, comme ci-dessus
- Si une sync bidirectionnelle est nécessaire, utiliser des horodatages ou des numéros de version et une règle claire: dernière écriture gagne, système source gagne, ou conflits envoyés à un humain
- Éviter de synchroniser des champs dérivés (totaux, statuts) que chaque système calcule différemment
- Attention aux boucles de sync: une mise à jour dans A déclenche une mise à jour dans B, qui déclenche une mise à jour dans A. Coupez la boucle en enregistrant l'origine de chaque changement et en ignorant ceux que vous avez faits vous-même
Panne 4: Des modèles de données incompatibles
Ce qui se passe: les deux systèmes ne décrivent pas le monde de la même façon. L'un a des « variantes », l'autre des produits séparés. L'un stocke un nom complet, l'autre prénom et nom. L'un a des taxes incluses dans les prix, l'autre les ajoute. L'un compte le stock « disponible », l'autre le stock « physique ».
Comment concevoir contre:
- Écrire un document de mapping champ par champ, avec les règles de transformation et des exemples
- Traiter explicitement les cas gênants: produits à variantes, lots, remboursements partiels, remises, adresses de livraison multiples, règles de TVA par pays
- Normaliser les formats: dates et fuseaux, devises et décimales (stocker l'argent en entiers ou décimales exactes, jamais en virgule flottante), téléphones et pays, encodage des caractères
- Valider à l'entrée, et rejeter ou signaler les enregistrements qui ne rentrent pas plutôt que de deviner
- Tester avec de vraies données moches: le client avec une apostrophe dans le nom, la commande à 40 lignes, le produit sans SKU
Le stock mérite une mention à part. Le « stock disponible » dépend des réservations, commandes en cours, retours et entrepôts. Décidez exactement quel chiffre vous synchronisez, et quand.
Panne 5: La panne silencieuse
Ce qui se passe: la sync s'arrête, ou fonctionne mal, et personne ne s'en aperçoit pendant des jours ou des semaines. Quand quelqu'un le voit, il y a des centaines d'incohérences.
Comment concevoir contre:
- Surveiller l'absence de succès, pas seulement les erreurs: « aucune commande importée en 3 heures pendant les heures ouvrées » doit lever une alerte
- Suivre les compteurs: enregistrements envoyés vs reçus, par jour
- Logger chaque run avec un résumé: traités, créés, mis à jour, ignorés, en échec
- Un tableau de bord ou un digest quotidien qui montre la santé d'un coup d'œil
- Un outil de re-sync manuel pour un seul enregistrement, pour que le support puisse corriger sans développeur
- Identifiants qui expirent: jetons d'API et connexions OAuth expirent. Suivre les dates d'expiration et alerter avant qu'elles ne passent
Webhooks, polling, ou les deux ?
- Webhooks: la source vous dit quand quelque chose change. Rapide et efficace, mais ils peuvent être perdus ou dupliqués, et votre endpoint doit être disponible et sécurisé (vérifier les signatures).
- Polling: vous demandez régulièrement les changements depuis votre dernier contrôle. Plus simple à raisonner, un peu plus lent, et ça consomme du quota d'API.
- Les deux: webhooks pour la vitesse, un job de polling ou de réconciliation comme filet de sécurité. C'est l'option robuste pour tout ce qui compte.
Respectez les limites de débit d'API: utilisez le batching quand il existe, faites un backoff quand vous recevez « too many requests », et évitez de tout récupérer à chaque run. Utilisez des filtres « changed since » ou des curseurs.
Un design minimal et robuste
- Les événements de la source vont d'abord dans une file (ou une table), jamais traités en ligne dans le handler webhook.
- Un worker traite les événements, de façon idempotente, avec retries.
- Après N échecs, les éléments vont dans une liste de revue avec alertes.
- Une réconciliation nocturne compare les deux systèmes et répare les écarts.
- Chaque enregistrement stocke son ID externe et la dernière heure de sync.
- Un tableau de bord ou un digest montre compteurs, erreurs et retard.
- Secrets, jetons et clés restent dans votre coffre, avec suivi d'expiration.
Pièges fréquents
- Sync bidirectionnelle partout « au cas où »
- Pas d'ID externes stockés, donc le matching repose sur noms ou emails
- Faire confiance aux seuls webhooks
- Traiter le webhook dans la requête, puis timeout
- Virgule flottante pour l'argent
- Confusion de fuseaux qui crée des erreurs d'un jour
- Pas d'alerte sur le silence
- Re-sync complet à chaque run, qui tape les rate limits
- Règles de cas limites codées en dur à plusieurs endroits
Checklist
- Source de vérité définie pour chaque type de donnée
- Document de mapping écrit, cas limites listés
- ID externes stockés des deux côtés
- Opérations idempotentes
- Retries avec backoff, et liste de revue pour les échecs répétés
- Job de réconciliation en place
- Règles de conflit définies pour tout champ bidirectionnel
- Alertes sur erreurs, silence et identifiants qui expirent
- Logs et résumé de santé disponibles
- Outil de correction manuelle pour un enregistrement
D'une sync fragile à un design opérable
Si votre boutique et votre back-office divergent déjà, ou si vous allez les brancher, écrivez-nous avant que l'écart ne devienne un incendie hebdomadaire.