Webhooks
A webhook is a name, a set of triggers, an endpoint and its settings. It fires when any of its triggers fires, and the payload names the one that did.
Manage them from your own tooling over /v1/webhooks (webhooks:read /
webhooks:write), or in the app.
Creating one
Section titled “Creating one”curl -X POST https://api.vendorica.com/v1/webhooks \ -H "Authorization: Bearer $VENDORICA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Drift alerts → PagerDuty", "url": "https://hooks.example.com/vendorica", "triggers": ["control.drift_detected", "vendor.rating_dropped"] }'GET /v1/webhooks/triggers lists every available trigger, annotated with
whether your plan includes it — read that rather than hard-coding names.
The delivery
Section titled “The delivery”POST /your-endpoint HTTP/1.1content-type: application/jsonuser-agent: Vendorica-Webhook/1x-vendorica-event: control.drift_detectedx-vendorica-delivery: 7c1e...x-vendorica-api-version: 2026-06-01{ "id": "7c1e...", "type": "control.drift_detected", "apiVersion": "2026-06-01", "createdAt": "2026-08-19T09:41:12.004Z", "organizationId": "3c91...", "data": { }}Verifying the signature
Section titled “Verifying the signature”Turn signing on and every delivery carries a timestamped HMAC:
x-vendorica-signature: t=1755594072,v1=6f3a...v1 is HMAC-SHA256(secret, "<t>.<raw request body>"), hex-encoded.
Verify against the raw body bytes, before any JSON parsing — a
re-serialised body will not match.
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) t, v1 = parts["t"], parts["v1"] if abs(time.time() - int(t)) > tolerance: # reject replays return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, v1) # constant timeThe timestamp is inside the signed string, so an attacker cannot replay an
old delivery with a fresh t. Reject anything outside your tolerance
window, and compare in constant time.
You can also attach custom headers for your receiver’s own auth — both the signing secret and those headers are encrypted at rest.
Delivery semantics — read this before you rely on it
Section titled “Delivery semantics — read this before you rely on it”The consequences you have to design around:
- Do not use webhooks as your only source of truth. Treat them as a low-latency hint, and reconcile periodically against the API. A nightly read of the relevant register turns a missed delivery into a delay instead of a permanent gap.
- A slow receiver is a lost event. Acknowledge with a
2xximmediately and do the work asynchronously. - After 15 consecutive failures the webhook auto-suspends, and your organization’s admins are emailed. A receiver that has been down over a weekend comes back to a suspended webhook, not a backlog — re-enable it, then reconcile for the gap.
GET /v1/webhooks/{id}/deliveries is the delivery log, cursor-paginated,
and is where you look when the receiver saw nothing.
Not to be confused with
Section titled “Not to be confused with”/v1/webhooks/inbound/* is a different thing entirely — that is where
third-party providers post to Vendorica. It is not part of this
management surface.