Real-time Bedrijfsdata Updates met Webhooks en Polling
Hoe je bedrijfsdata actueel houdt met polling, cache-invalidatie en periodieke hervalidatie. Praktische patronen voor je applicatie.
Real-time Bedrijfsdata Updates met Webhooks en Polling
Je hebt bedrijfsgegevens opgehaald bij de KVK en opgeslagen in je database. Maar hoe houd je die data actueel? Bedrijven verhuizen, veranderen van naam of worden uitgeschreven. Als je geen strategie hebt om wijzigingen op te vangen, werkt je applicatie binnen een paar maanden met verouderde data.
In dit artikel bespreken we de twee hoofdstrategieen — polling en webhooks — en geven we praktische implementatiepatronen voor het actueel houden van bedrijfsdata.
Het probleem: data veroudert
Bedrijfsgegevens zijn niet statisch. In een willekeurig jaar gebeurt het volgende in het handelsregister:
- Duizenden bedrijven veranderen van adres
- Bedrijven wijzigen hun handelsnaam
- Nieuwe bedrijven worden ingeschreven
- Bedrijven worden uitgeschreven of ontbonden
- Rechtsvormen veranderen (eenmanszaak wordt BV)
Als je 10.000 bedrijfsrecords in je database hebt, zijn er na zes maanden gegarandeerd honderden records die niet meer kloppen.
Strategie 1: Polling
Polling is de eenvoudigste aanpak: je controleert periodiek of de data van je opgeslagen bedrijven nog actueel is.
Basisimplementatie
const REFRESH_INTERVAL_DAYS = 7; // Hervalideer elke 7 dagen
async function refreshStaleCompanies() {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - REFRESH_INTERVAL_DAYS);
// Haal bedrijven op die langer dan 7 dagen niet zijn gecontroleerd
const staleCompanies = await db.companies.findWhere(
'last_verified_at < ? OR last_verified_at IS NULL',
cutoff
);
console.log(`${staleCompanies.length} bedrijven moeten worden gehervalideerd`);
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 {
// Geen wijzigingen, alleen de verificatiedatum bijwerken
await db.companies.update(company.id, {
lastVerifiedAt: new Date()
});
}
} catch (error) {
console.error(`Hervalidatie mislukt voor ${company.kvkNumber}:`, error);
}
// Pauze om rate limits te respecteren
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 });
}
// Vergelijk adresvelden...
return changes;
}
Slim plannen
Niet alle bedrijven hoeven even vaak gecontroleerd te worden. Prioriteer op basis van activiteit:
function getRefreshPriority(company) {
// Actieve klanten: elke 3 dagen
if (company.hasRecentOrders) return 3;
// Bedrijven met openstaande facturen: dagelijks
if (company.hasOpenInvoices) return 1;
// Inactieve klanten: elke 30 dagen
if (!company.hasRecentActivity) return 30;
// Standaard: elke 7 dagen
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);
}
}
}
Cron-job opzetten
Plan de polling als een cron-job die dagelijks draait:
# crontab -e
# Elke nacht om 03:00 de hervalidatie draaien
0 3 * * * node /app/scripts/refresh-companies.js
Of met een task scheduler in Node.js:
import cron from 'node-cron';
cron.schedule('0 3 * * *', async () => {
console.log('Start bedrijfsdata hervalidatie...');
await refreshStaleCompanies();
console.log('Hervalidatie afgerond');
});
Strategie 2: Webhooks
Webhooks zijn het omgekeerde van polling: in plaats van dat jij periodiek controleert, stuurt de databron een bericht naar jouw applicatie wanneer er iets verandert.
Webhook-endpoint opzetten
// Express endpoint voor webhook-notificaties
app.post('/webhooks/company-updates', async (req, res) => {
// Verifieer de webhook-handtekening
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(`Onbekend event type: ${eventType}`);
}
// Altijd 200 terugsturen om te bevestigen
res.status(200).json({ received: true });
});
Webhook-beveiliging
Webhooks moeten altijd geverifieerd worden. Vertrouw nooit blindelings op binnenkomende verzoeken:
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)
);
}
Idempotentie
Webhooks kunnen meerdere keren worden afgeleverd. Zorg dat je handler idempotent is:
async function handleCompanyUpdate(kvkNumber, data) {
const eventId = data.eventId;
// Controleer of we dit event al verwerkt hebben
const processed = await db.webhookEvents.findByEventId(eventId);
if (processed) {
console.log(`Event ${eventId} al verwerkt, overgeslagen`);
return;
}
// Verwerk de update
await db.companies.updateByKvk(kvkNumber, {
tradeName: data.tradeName,
address: data.address,
isActive: data.isActive,
lastVerifiedAt: new Date()
});
// Markeer event als verwerkt
await db.webhookEvents.create({
eventId,
kvkNumber,
processedAt: new Date()
});
}
Cache-invalidatie
Los van polling en webhooks heb je een cache-invalidatiestrategie nodig voor real-time lookups.
TTL-gebaseerde invalidatie
De eenvoudigste aanpak: stel een Time-To-Live in op je cache-entries:
const CACHE_TTL = {
active: 24 * 60 * 60, // 24 uur voor actieve bedrijven
inactive: 7 * 24 * 60 * 60, // 7 dagen voor inactieve bedrijven
notFound: 60 * 60, // 1 uur voor niet-gevonden nummers
search: 60 * 60 // 1 uur voor zoekresultaten
};
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
Een geavanceerder patroon: stuur de gecachte data direct terug, maar vernieuw op de achtergrond:
async function lookupStaleWhileRevalidate(kvkNumber) {
const cached = await cache.get(`company:${kvkNumber}`);
if (cached) {
// Vernieuw op de achtergrond als de data ouder is dan 12 uur
const age = Date.now() - cached.cachedAt;
if (age > 12 * 60 * 60 * 1000) {
refreshInBackground(kvkNumber); // fire-and-forget
}
return cached.data;
}
// Geen cache: wacht op verse data
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}`));
}
Monitoring
Welke strategie je ook kiest, zet monitoring op om te weten of je data actueel is:
async function reportDataFreshness() {
const stats = await db.companies.aggregate([
{
group: 'freshness',
buckets: [
{ label: 'vandaag', where: 'last_verified_at >= NOW() - INTERVAL 1 DAY' },
{ label: 'deze_week', where: 'last_verified_at >= NOW() - INTERVAL 7 DAY' },
{ label: 'deze_maand', where: 'last_verified_at >= NOW() - INTERVAL 30 DAY' },
{ label: 'ouder', where: 'last_verified_at < NOW() - INTERVAL 30 DAY' }
]
}
]);
console.log('Data-versheid rapport:', stats);
}
Welke strategie kies je?
| Factor | Polling | Webhooks |
|---|---|---|
| Complexiteit | Laag | Middel |
| Real-time | Nee (vertraagd) | Ja |
| Kosten (API-calls) | Hoger | Lager |
| Betrouwbaarheid | Hoog (jij hebt controle) | Afhankelijk van provider |
| Beste voor | Kleine datasets, nachtelijke sync | Grote datasets, real-time vereisten |
In de praktijk werkt een combinatie het best: polling als basis met webhooks als versneller wanneer beschikbaar.
Conclusie
Het actueel houden van bedrijfsdata is net zo belangrijk als het ophalen ervan. Zonder een strategie voor updates werkt je applicatie binnen maanden met verouderde informatie.
Begin met een eenvoudige polling-strategie: een nachtelijke cron-job die bedrijven hervalideert op basis van prioriteit. Voeg webhooks toe wanneer je schaalbehoeften groeien. En implementeer altijd slimme caching met TTL-gebaseerde invalidatie voor je dagelijkse lookups.
Met KVKBase als databron kun je beide strategieen eenvoudig implementeren — de API is snel genoeg voor polling en ondersteunt de patronen die je nodig hebt voor betrouwbare bedrijfsdata.