Webhooks
Instead of polling, vutuv can POST signed event notifications to your server as things happen: a new follower, a connection request, a reply, a message.
Webhooks belong to a registered OAuth application.
Add one on your app's page under
/developers/apps: endpoint URL (https; http://localhost
for development), the events you care about, and you receive a signing
secret (vutuv_whsec_…, shown once).
Privacy model: thin envelopes, scoped fan-out
Two rules shape everything:
-
Your app only hears about members who authorized it — with the
scope matching the event. A
message.createdevent for member M is only delivered if M granted your appmessages:read. No grant, no delivery; revoked grant, no delivery; suspended app, no delivery. - Payloads carry ids and usernames, never content. A message event does not contain the message text; a reply event not the reply body. Fetch details through the API with your scoped token — content never sits unencrypted in webhook logs.
Events
| Event | Needs the member's scope |
data |
|---|---|---|
follower.created |
social:read |
follower (username) |
connection.requested |
social:read |
from |
connection.accepted |
social:read |
by |
endorsement.created |
social:read |
endorser, tag |
post.liked |
posts:read |
by, post_id |
post.replied |
posts:read |
by, post_id |
message.created |
messages:read |
from, conversation_id |
Plus ping, the test event you can trigger from the app page.
The delivery
POST <your endpoint>
Content-Type: application/json
X-Vutuv-Event: follower.created
X-Vutuv-Delivery: 0190… (unique per delivery — deduplicate on it)
X-Vutuv-Signature: sha256=8d7c…
{
"event": "follower.created",
"occurred_at": "2026-06-11T14:00:00Z",
"member": "wintermeyer",
"data": {"follower": "greta-tester"}
}
member is whose authorization the delivery rides on — the member the
event happened to.
Verify the signature — always
The signature is a hex HMAC-SHA256 of the raw request body with your signing secret. Reject anything that does not verify; an unverified webhook endpoint is an open door for fabricated events.
Python:
import hashlib, hmac, os
def verify(raw_body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(
os.environ["VUTUV_WEBHOOK_SECRET"].encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
Node:
const crypto = require("node:crypto");
function verify(rawBody, signatureHeader) {
const expected = "sha256=" +
crypto.createHmac("sha256", process.env.VUTUV_WEBHOOK_SECRET)
.update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
Respond fast, retry contract
Answer with any 2xx within 10 seconds — acknowledge first, process
later. Anything else (including timeouts) is retried with exponential
backoff (2, 4, 8, … minutes, up to 8 attempts per event). Deliveries can
arrive out of order and, in rare cases, more than once — deduplicate on
X-Vutuv-Delivery.
An endpoint that does nothing but fail is automatically disabled after sustained failures; the app page shows the reason and a re-enable button. Events that occurred while a subscription was disabled are not replayed — webhooks are a freshness channel, the API is the source of truth.
Local development
Point the webhook at an http://localhost URL (allowed only for
development) or use a tunnel. The Send ping button on the app page
delivers a ping event so you can verify your signature code without
waiting for a real event.