A webhook is a URL on your side that VenueDB calls when something happens. Use them to post to a chat channel, update a CRM or start an automation without polling the API.
Set one up
- Sign in as the owner and open Settings → Webhooks.
- Add the URL, choose the events you want, and choose the format (below).
- Copy the signing secret shown after you save it. It is shown only once. You use it to verify that requests really came from VenueDB.
Events
| Event | Fires when |
|---|---|
lead.created | A lead is created in the app (new-lead form or inquiry capture). Leads created through the API or a CSV import do not fire this event. |
event.confirmed | An event is confirmed. |
event.stage_changed | An event moves to a different lifecycle stage. |
A subscription can listen to specific events or to everything.
Formats
| Format | Sends |
|---|---|
| Custom | A JSON envelope for your own code (below). |
| Slack | Slack Block Kit messages, ready to post to an incoming-webhook URL. |
| Discord | Discord embeds. |
| Google Chat | Google Chat cards. |
The Slack, Discord and Google Chat formats are designed to be pasted into those tools' incoming-webhook URLs. They can be customized in the webhook's template options.
The Custom payload
{
"event": "event.stage_changed",
"data": {
"id": "ev_0042",
"eventName": "Bennett–Sutton Wedding",
"eventType": "wedding",
"eventDate": "2027-05-15T21:30:00.000Z",
"fromStage": "Tentative",
"stage": "Definite",
"lifecycle": "Long lead (over 6 months)",
"venueId": "cm1…",
"venueName": "Village Event Center"
},
"deliveredAt": "2026-10-06T14:05:31.000Z"
}
The data object differs per event:
lead.created — id, firstName, lastName, email, eventType, status, createdAt
event.confirmed — id, eventName, eventType, eventDate, guestCount, venueId, venueName
event.stage_changed — id, eventName, eventType, eventDate, fromStage, stage, lifecycle, venueId, venueName
Each request also carries these headers:
| Header | Value |
|---|---|
X-VenueDB-Event | The event name, e.g. lead.created. |
X-VenueDB-Signature | sha256= followed by the HMAC signature of the body. |
Verify the signature
The signature is an HMAC-SHA256 of the raw request body using your signing secret, hex-encoded. Compute it yourself and compare it to the header. Always compare in constant time, and use the raw bytes — not a re-serialized copy of the JSON.
import crypto from "node:crypto";
function isFromVenueDB(rawBody, signatureHeader, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Delivery and retries
- Deliveries are sent in the background, so a slow or offline endpoint never slows down the action that triggered it.
- Your endpoint should answer with a
2xxstatus within 10 seconds. - A failed delivery is retried up to 5 times with exponentially increasing delays.
- Each attempt's result (status code and any error) is recorded and visible in the webhook's delivery history in Settings → Webhooks.
Because retries can deliver the same event more than once, make your handler idempotent: use the id in data (plus the event name) to ignore repeats.
Knowing when deliveries fail
You don't have to watch the delivery log. Each webhook's health is shown under Dashboard → Background jobs → Connections: three failed deliveries in a row marks it as a problem (one or two is a warning), and it recovers after two successes in a row. Owners, admins and managers are notified once when it breaks and once when it's working again. View deliveries takes you straight to the log.
