Back to blog
6 min read

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:

A monitor event payloadjson
{
  "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:

Verifying an HMAC-signed webhookjs
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.

Keep reading