Données d'entreprise à jour : webhooks et polling
KVKBase Team

Données d'entreprise à jour : webhooks et polling

Comment garder les données d'entreprise à jour : polling, invalidation du cache et revalidation périodique. Des schémas concrets pour votre application.

webhooksapitemps-réel

Vous avez récupéré des données d’entreprise auprès du KVK et les avez stockées dans votre base de données. Mais comment les garder à jour ? Les entreprises déménagent, changent de nom ou sont radiées. Sans stratégie pour détecter les changements, votre application travaillera avec des données périmées au bout de quelques mois.

Dans cet article, nous présentons les deux grandes stratégies, le polling (interrogation régulière) et les webhooks, et proposons des schémas d’implémentation concrets pour garder vos données d’entreprise à jour.

Le problème : les données se périment

Les données d’entreprise ne sont pas figées. Chaque année, dans le registre du commerce :

  • Des milliers d’entreprises changent d’adresse
  • Des entreprises changent de nom commercial
  • De nouvelles entreprises sont immatriculées
  • Des entreprises sont radiées ou dissoutes
  • Des formes juridiques changent (une entreprise individuelle devient une BV)

Si votre base compte 10 000 fiches d’entreprises, des centaines d’entre elles seront forcément inexactes au bout de six mois.

Stratégie 1 : le polling

Le polling est l’approche la plus simple : vous vérifiez périodiquement que les données des entreprises stockées sont toujours à jour.

Implémentation de base

const REFRESH_INTERVAL_DAYS = 7; // Revalider tous les 7 jours

async function refreshStaleCompanies() {
  const cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - REFRESH_INTERVAL_DAYS);

  // Récupérer les entreprises non vérifiées depuis plus de 7 jours
  const staleCompanies = await db.companies.findWhere(
    'last_verified_at < ? OR last_verified_at IS NULL',
    cutoff
  );

  console.log(`${staleCompanies.length} companies need revalidation`);

  for (const company of staleCompanies) {
    try {
      const fresh = await kvkbase.lookup(company.kvkNumber);
      const changes = detectChanges(company, fresh);

      if (changes.length > 0) {
        await db.companies.update(company.id, {
          tradeName: fresh.tradeName,
          address: fresh.address,
          isActive: fresh.isActive,
          lastVerifiedAt: new Date()
        });

        await logChanges(company.kvkNumber, changes);
      } else {
        // Aucun changement : mettre simplement à jour la date de vérification
        await db.companies.update(company.id, {
          lastVerifiedAt: new Date()
        });
      }
    } catch (error) {
      console.error(`Revalidation failed for ${company.kvkNumber}:`, error);
    }

    // Pause pour respecter les limites de débit
    await sleep(200);
  }
}

function detectChanges(old, fresh) {
  const changes = [];

  if (old.tradeName !== fresh.tradeName) {
    changes.push({ field: 'tradeName', old: old.tradeName, new: fresh.tradeName });
  }
  if (old.isActive !== fresh.isActive) {
    changes.push({ field: 'isActive', old: old.isActive, new: fresh.isActive });
  }
  // Comparer les champs d'adresse...

  return changes;
}

Planification intelligente

Toutes les entreprises n’ont pas besoin d’être vérifiées à la même fréquence. Priorisez selon l’activité :

function getRefreshPriority(company) {
  // Clients actifs : tous les 3 jours
  if (company.hasRecentOrders) return 3;

  // Entreprises avec des factures ouvertes : tous les jours
  if (company.hasOpenInvoices) return 1;

  // Clients inactifs : tous les 30 jours
  if (!company.hasRecentActivity) return 30;

  // Par défaut : tous les 7 jours
  return 7;
}

async function refreshByPriority() {
  const companies = await db.companies.findAll();

  for (const company of companies) {
    const priority = getRefreshPriority(company);
    const daysSinceRefresh = daysBetween(company.lastVerifiedAt, new Date());

    if (daysSinceRefresh >= priority) {
      await refreshCompany(company);
    }
  }
}

Mettre en place une tâche cron

Planifiez le polling comme une tâche cron exécutée chaque jour :

# crontab -e
# Lancer la revalidation chaque nuit à 03:00
0 3 * * * node /app/scripts/refresh-companies.js

Ou avec un planificateur de tâches en Node.js :

import cron from 'node-cron';

cron.schedule('0 3 * * *', async () => {
  console.log('Starting company data revalidation...');
  await refreshStaleCompanies();
  console.log('Revalidation complete');
});

Stratégie 2 : les webhooks

Les webhooks sont l’inverse du polling : au lieu de vérifier vous-même périodiquement, la source de données envoie une notification à votre application lorsque quelque chose change.

Mettre en place un point d’accès webhook

// Point d'accès Express pour les notifications webhook
app.post('/webhooks/company-updates', async (req, res) => {
  // Vérifier la signature du webhook
  const signature = req.headers['x-webhook-signature'];
  if (!verifySignature(req.body, signature)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { kvkNumber, eventType, data } = req.body;

  switch (eventType) {
    case 'company.updated':
      await handleCompanyUpdate(kvkNumber, data);
      break;
    case 'company.deregistered':
      await handleCompanyDeregistered(kvkNumber);
      break;
    case 'company.address_changed':
      await handleAddressChange(kvkNumber, data);
      break;
    default:
      console.log(`Unknown event type: ${eventType}`);
  }

  // Toujours renvoyer 200 pour accuser réception
  res.status(200).json({ received: true });
});

Sécurité des webhooks

Les webhooks doivent toujours être vérifiés. Ne faites jamais aveuglément confiance aux requêtes entrantes :

const crypto = require('crypto');

function verifySignature(payload, signature) {
  const secret = process.env.WEBHOOK_SECRET;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Idempotence

Un webhook peut être livré plus d’une fois. Assurez-vous que votre gestionnaire est idempotent :

async function handleCompanyUpdate(kvkNumber, data) {
  const eventId = data.eventId;

  // Vérifier si cet événement a déjà été traité
  const processed = await db.webhookEvents.findByEventId(eventId);
  if (processed) {
    console.log(`Event ${eventId} already processed, skipping`);
    return;
  }

  // Traiter la mise à jour
  await db.companies.updateByKvk(kvkNumber, {
    tradeName: data.tradeName,
    address: data.address,
    isActive: data.isActive,
    lastVerifiedAt: new Date()
  });

  // Marquer l'événement comme traité
  await db.webhookEvents.create({
    eventId,
    kvkNumber,
    processedAt: new Date()
  });
}

Invalidation du cache

En plus du polling et des webhooks, il vous faut une stratégie d’invalidation du cache pour les recherches en temps réel.

Invalidation basée sur la durée de vie (TTL)

L’approche la plus simple : définir une durée de vie (Time-To-Live) sur vos entrées de cache :

const CACHE_TTL = {
  active: 24 * 60 * 60,        // 24 heures pour les entreprises actives
  inactive: 7 * 24 * 60 * 60,  // 7 jours pour les entreprises inactives
  notFound: 60 * 60,            // 1 heure pour les numéros introuvables
  search: 60 * 60               // 1 heure pour les résultats de recherche
};

async function lookupWithSmartCache(kvkNumber) {
  const cached = await cache.get(`company:${kvkNumber}`);

  if (cached) {
    return cached;
  }

  const fresh = await kvkbase.lookup(kvkNumber);
  const ttl = fresh
    ? (fresh.isActive ? CACHE_TTL.active : CACHE_TTL.inactive)
    : CACHE_TTL.notFound;

  await cache.set(`company:${kvkNumber}`, fresh, ttl);
  return fresh;
}

Stale-While-Revalidate

Un schéma plus avancé : renvoyer immédiatement les données en cache, mais les actualiser en arrière-plan :

async function lookupStaleWhileRevalidate(kvkNumber) {
  const cached = await cache.get(`company:${kvkNumber}`);

  if (cached) {
    // Actualiser en arrière-plan si les données ont plus de 12 heures
    const age = Date.now() - cached.cachedAt;
    if (age > 12 * 60 * 60 * 1000) {
      refreshInBackground(kvkNumber); // lancer sans attendre
    }

    return cached.data;
  }

  // Pas de cache : attendre des données fraîches
  const fresh = await kvkbase.lookup(kvkNumber);
  await cache.set(`company:${kvkNumber}`, {
    data: fresh,
    cachedAt: Date.now()
  });

  return fresh;
}

function refreshInBackground(kvkNumber) {
  kvkbase.lookup(kvkNumber)
    .then(fresh => cache.set(`company:${kvkNumber}`, {
      data: fresh,
      cachedAt: Date.now()
    }))
    .catch(err => console.error(`Background refresh failed: ${err.message}`));
}

Supervision

Quelle que soit la stratégie choisie, mettez en place une supervision pour savoir si vos données sont à jour :

async function reportDataFreshness() {
  const stats = await db.companies.aggregate([
    {
      group: 'freshness',
      buckets: [
        { label: 'today', where: 'last_verified_at >= NOW() - INTERVAL 1 DAY' },
        { label: 'this_week', where: 'last_verified_at >= NOW() - INTERVAL 7 DAY' },
        { label: 'this_month', where: 'last_verified_at >= NOW() - INTERVAL 30 DAY' },
        { label: 'older', where: 'last_verified_at < NOW() - INTERVAL 30 DAY' }
      ]
    }
  ]);

  console.log('Data freshness report:', stats);
}

Quelle stratégie choisir ?

CritèrePollingWebhooks
ComplexitéFaibleMoyenne
Temps réelNon (différé)Oui
Coût (appels d’API)Plus élevéPlus faible
FiabilitéÉlevée (vous gardez la main)Dépend du fournisseur
Idéal pourPetits jeux de données, synchronisation nocturneGrands jeux de données, exigences de temps réel

En pratique, une combinaison fonctionne le mieux : le polling comme base, avec les webhooks comme accélérateur lorsqu’ils sont disponibles.

Conclusion

Garder les données d’entreprise à jour est tout aussi important que de les récupérer au départ. Sans stratégie de mise à jour, votre application travaillera avec des informations périmées en quelques mois.

Commencez par une stratégie de polling simple : une tâche cron nocturne qui revalide les entreprises selon leur priorité. Ajoutez les webhooks quand vos besoins de montée en charge augmentent. Et mettez toujours en place un cache intelligent avec invalidation par TTL pour vos recherches du quotidien.

Avec KVKBase comme source de données, vous pouvez facilement mettre en œuvre les deux stratégies : l’API est assez rapide pour le polling et prend en charge les schémas dont vous avez besoin pour des données d’entreprise fiables.