Subscribe to real-time events when bookings are created, updated, or cancelled.
Vennio sends HTTP POST requests to your registered URL whenever a subscribed event occurs. Webhook deliveries include a signature header for verification and are retried up to 3 times with exponential backoff.
| Event | Description |
|---|---|
booking.created |
A new booking was made |
booking.updated |
A booking was rescheduled |
booking.confirmed |
A paid booking was confirmed after successful payment |
booking.cancelled |
A booking was cancelled |
consent.granted |
A user granted calendar access |
consent.revoked |
A user revoked calendar access |
curl -X POST https://api.vennio.app/v1/webhooks \
-H "Authorization: Bearer vennio_sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/vennio",
"events": ["booking.created", "booking.cancelled"],
"description": "My booking notifications"
}'
{
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/vennio",
"events": ["booking.created", "booking.cancelled"],
"secret": "whsec_xxxxxxxxxxxxxxxxxxxx",
"created_at": "2026-01-20T10:00:00Z"
}
The webhook signing secret is only shown once at creation. Use it to verify incoming payloads.
Every webhook delivery includes a X-Webhook-Signature header containing an HMAC-SHA256 signature of the raw request body. Always verify this before processing.
import crypto from 'crypto'
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('hex')
if (signature.length !== expected.length) return false
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
}
// Express handler
app.post('/webhooks/vennio', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature']
if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' })
}
const event = JSON.parse(req.body)
console.log('Event received:', event.type)
// Process event...
res.json({ received: true })
})
If your endpoint returns a non-2xx status or times out, Vennio retries with backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| Failed | Marked as failed after 4 total attempts |
View delivery history at GET /v1/webhooks/{id}/deliveries.
All events follow a consistent envelope:
{
"id": "evt_abc123",
"type": "booking.created",
"created_at": "2026-01-20T14:30:00Z",
"data": {
"booking": {
"id": "booking_xyz789",
"status": "confirmed",
"start_time": "2026-01-21T09:00:00Z",
"end_time": "2026-01-21T09:30:00Z",
"customer_email": "customer@example.com",
"customer_name": "Jane Doe"
}
}
}
For paid bookings, the booking.confirmed event includes payment details:
{
"id": "evt_def456",
"type": "booking.confirmed",
"created_at": "2026-03-15T10:05:00Z",
"data": {
"booking": {
"id": "booking_xyz789",
"status": "confirmed",
"start_time": "2026-03-15T10:00:00Z",
"end_time": "2026-03-15T10:30:00Z",
"customer_email": "customer@example.com",
"customer_name": "Jane Doe",
"payment_amount": 5000,
"payment_currency": "usd"
}
}
}