Real-Time Webhooks for Company Monitoring
By Kooperativa Engineering
Tracking whether a person or company has changed over time can be built two ways. Polling re-checks a record on a schedule and diffs it against the last known state. Webhooks flip the direction: the data provider notifies a URL you control the moment a change is actually detected, and nothing fires when nothing changed.
The practical cost of polling is not obvious until it is running: most calls in a polling loop are spent confirming that nothing changed, which is the one outcome that never needed a call in the first place. Webhooks remove that waste entirely, at the cost of trusting the provider to actually detect the change promptly.
What a usable webhook payload includes
A notification that only says "something changed" forces a follow-up lookup to find out what, which defeats most of the point. A useful payload states the specific field and both the old and new value directly:
{
"id": "c1c882c8-f172-4c00-8efc-be6fa9e3add4",
"event": "person.job_changed",
"monitor_id": "45e0bba8-196d-4f5e-aecc-52332ca832e2",
"timestamp": "1752918000",
"diff": {
"old_company": "Microsoft",
"new_company": "OpenAI",
"old_title": "CEO",
"new_title": "Board Member"
}
}Verifying a webhook is not optional
A webhook URL is a public endpoint by definition, which means anyone who finds it can send it a fabricated payload. The fix is a signature computed over the request timestamp and body using a per-monitor secret, checked before the payload is trusted:
import crypto from "crypto";
function verifyWebhook(req, secret) {
const timestamp = req.headers["x-kooperativa-timestamp"];
const signature = req.headers["x-kooperativa-signature"];
const expected = crypto
.createHmac("sha256", secret)
.update(`v0:${timestamp}:${JSON.stringify(req.body)}`)
.digest("hex");
return expected === signature;
}The retry problem this creates, and how to handle it
A webhook system that retries on a non-2xx response, which any reliable one does, will occasionally deliver the same event twice: once that timed out on your end after actually succeeding, then a retry. A handler that is not idempotent will process that event twice, which for something like a CRM update means a duplicate write. The fix is to key deduplication off a unique delivery ID included in every payload, and skip anything already processed, rather than assuming each delivery is guaranteed to be exactly-once.
Get started
Try Kooperativa
One API key. Person and company enrichment, structured search, and monitors under one flat license.
