Skip to content
VynCo is in public beta — we'd love your feedback.
← Back to blogServing a Real-Time Swiss Company Change Feed with Webhooks

Serving a Real-Time Swiss Company Change Feed with Webhooks

VynCo Engineering3 min read4/14/2026

Poll-based monitoring wastes compute and latency. A 1 000-company watchlist polled every 15 minutes is 96 000 needless requests per day — almost all of which return no change. Webhooks flip the arrow: our infra calls yours when something relevant happens.

Compliance dashboard

60-second setup

import vynco
client = vynco.Client()

# 1. Group the companies you care about
wl = client.watchlists.create(name="High-risk counterparties").data
client.watchlists.add_companies(wl.id, uids=["CHE-101.329.561", "CHE-105.805.080", ...])

# 2. Subscribe your webhook endpoint to the events you want
hook = client.webhooks.create(
    url="https://your-app.example/vynco-hook",
    events=[
        "sanctions.match",      # a watched company hit a sanctions list
        "status.change",        # Active → In Liquidation / Deleted
        "capital.change",       # share capital up or down
        "auditor.change",
        "board.change",         # person added / removed
        "address.change",
    ],
    secret="your-hmac-shared-secret",
).data

print(f"Subscribed webhook {hook.id} → {hook.url}")

That's it. Now every time a watched company has an event, a POST lands at your URL within seconds.

The receiver side

A typical handler in FastAPI:

from fastapi import FastAPI, Header, HTTPException, Request
import hmac, hashlib

app = FastAPI()
SECRET = "your-hmac-shared-secret"

@app.post("/vynco-hook")
async def receive(request: Request, x_vynco_signature: str = Header(...)):
    body = await request.body()

    # Verify HMAC — prevents spoofing
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, x_vynco_signature):
        raise HTTPException(401, "bad signature")

    evt = await request.json()

    if evt["eventType"] == "sanctions.match":
        alert_team(evt["companyUid"], evt["detail"])
    elif evt["eventType"] == "capital.change":
        old, new = evt["detail"]["oldValue"], evt["detail"]["newValue"]
        log.info(f"{evt['companyName']} capital {old} → {new}")

    return {"ok": True}

The SDK ships a matching verifier so you don't hand-roll the HMAC comparison:

from vynco.webhooks import verify

payload = await request.body()
evt = verify(payload, x_vynco_signature, secret=SECRET)  # raises on mismatch

Delivery guarantees

  • At-least-once delivery. We retry failed deliveries with exponential backoff (1s, 5s, 25s, 2m, 10m, 1h) up to 6 times before giving up. Persist your own event-id dedup if strict exactly-once matters.
  • Ordering is not guaranteed. Events for a single company are usually in order, but across companies are delivered in parallel. Every payload includes a ceTime ISO-8601 timestamp — sort on that if order matters to you.
  • Retry history is queryable. GET /v1/webhooks/{id}/deliveries returns the last 100 attempts with response codes and bodies. Useful when you flip your firewall and want to see what we tried to deliver during the outage.

Test before flipping on

You don't need to wait for a real event to debug your handler:

client.webhooks.test_delivery(hook.id)

Fires a synthetic webhook.test event with the full signature flow, so you can verify your endpoint end-to-end.

Watchlist scoping vs global scoping

Two patterns work:

Watchlist-scoped (shown above): one webhook subscribes to events for a specific watchlist. If you have multiple teams with different portfolios, give each team its own webhook + watchlist.

Global scope: omit watchlist_id on creation and the webhook receives events for every company in your account's universe. Higher volume — useful for building a firehose you can post-filter downstream.

Example script

examples/watchlist_monitor.py is a complete end-to-end runner: it creates a watchlist, adds 5 well-known Swiss companies, subscribes a webhook (pointed at requestbin.com for local testing), and polls the deliveries endpoint to show what arrived.

Rate limits

Webhooks deliveries don't count against your monthly credit balance. Inbound API calls (creating/updating/listing webhooks, adding to watchlists) do, at the standard 1-credit rate.

Links