
Serving a Real-Time Swiss Company Change Feed with Webhooks
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.

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
ceTimeISO-8601 timestamp — sort on that if order matters to you. - Retry history is queryable.
GET /v1/webhooks/{id}/deliveriesreturns 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
- Example: watchlist_monitor.py
- API docs: vynco.ch/docs/webhooks
- Get an API key: vynco.ch/signup