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.
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ère | Polling | Webhooks |
|---|---|---|
| Complexité | Faible | Moyenne |
| Temps réel | Non (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 pour | Petits jeux de données, synchronisation nocturne | Grands 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.