Webhooks

Punctual Webhooks

Get real-time notifications about events in your Punctual account. Build powerful integrations that respond instantly to scheduling changes.

Why use webhooks?

Powerful features for real-time integration

Secure Delivery

All webhooks are signed with HMAC-SHA256 to ensure authenticity and prevent tampering

Automatic Retries

Failed webhook deliveries are automatically retried with exponential backoff

Real-time Events

Receive events instantly as they happen in your Punctual account

Event History

Complete history of all webhook events with delivery status and response codes

Easy Configuration

Simple setup through our dashboard with instant testing capabilities

Global Reliability

Webhooks are delivered from multiple data centers for maximum reliability

Available Events

All the events you can subscribe to

booking.created

v1

Triggered when a new booking is created

JSON
{
  "id": "evt_1234567890",
  "type": "booking.created",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "object": {
      "id": "booking_123",
      "title": "Product Demo",
      "start_time": "2024-01-20T14:00:00Z",
      "end_time": "2024-01-20T15:00:00Z",
      "attendee_email": "client@example.com",
      "status": "confirmed",
      "created_at": "2024-01-15T10:30:00Z"
    }
  }
}

booking.updated

v1

Triggered when a booking is modified

JSON
{
  "id": "evt_1234567891",
  "type": "booking.updated",
  "created": "2024-01-15T11:00:00Z",
  "data": {
    "object": {
      "id": "booking_123",
      "title": "Updated Product Demo",
      "start_time": "2024-01-20T15:00:00Z",
      "end_time": "2024-01-20T16:00:00Z",
      "attendee_email": "client@example.com",
      "status": "confirmed",
      "updated_at": "2024-01-15T11:00:00Z"
    }
  }
}

booking.cancelled

v1

Triggered when a booking is cancelled

JSON
{
  "id": "evt_1234567892",
  "type": "booking.cancelled",
  "created": "2024-01-15T12:00:00Z",
  "data": {
    "object": {
      "id": "booking_123",
      "title": "Product Demo",
      "start_time": "2024-01-20T14:00:00Z",
      "end_time": "2024-01-20T15:00:00Z",
      "attendee_email": "client@example.com",
      "status": "cancelled",
      "cancelled_at": "2024-01-15T12:00:00Z"
    }
  }
}

availability.updated

v1

Triggered when user availability changes

JSON
{
  "id": "evt_1234567893",
  "type": "availability.updated",
  "created": "2024-01-15T13:00:00Z",
  "data": {
    "object": {
      "user_id": "user_123",
      "date": "2024-01-20",
      "slots": [
        {
          "start": "09:00",
          "end": "10:00",
          "available": true
        },
        {
          "start": "10:00",
          "end": "11:00",
          "available": false
        },
        {
          "start": "11:00",
          "end": "12:00",
          "available": true
        }
      ],
      "updated_at": "2024-01-15T13:00:00Z"
    }
  }
}

user.created

v1

Triggered when a new user signs up

JSON
{
  "id": "evt_1234567894",
  "type": "user.created",
  "created": "2024-01-15T14:00:00Z",
  "data": {
    "object": {
      "id": "user_456",
      "email": "newuser@example.com",
      "name": "John Doe",
      "created_at": "2024-01-15T14:00:00Z"
    }
  }
}

team.member_added

v1

Triggered when a new member is added to a team

JSON
{
  "id": "evt_1234567895",
  "type": "team.member_added",
  "created": "2024-01-15T15:00:00Z",
  "data": {
    "object": {
      "team_id": "team_789",
      "user_id": "user_456",
      "role": "member",
      "added_at": "2024-01-15T15:00:00Z"
    }
  }
}

Code Examples

Get started quickly with these implementation examples

Nodejs

nodejs
const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['punctual-signature'];
  const payload = req.body;
  
  // Verify webhook signature
  const expectedSignature = crypto
    .createHmac('sha256', process.env.PUNCTUAL_WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  if (signature !== expectedSignature) {
    return res.status(400).send('Invalid signature');
  }
  
  const event = JSON.parse(payload);
  
  // Handle the event
  switch (event.type) {
    case 'booking.created':
      console.log('New booking:', event.data.object);
      break;
    case 'booking.updated':
      console.log('Booking updated:', event.data.object);
      break;
    case 'booking.cancelled':
      console.log('Booking cancelled:', event.data.object);
      break;
    default:
      console.log('Unhandled event type:', event.type);
  }
  
  res.json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Python

python
import hmac
import hashlib
import json
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('Punctual-Signature')
    payload = request.get_data()
    
    # Verify webhook signature
    expected_signature = hmac.new(
        bytes(os.environ['PUNCTUAL_WEBHOOK_SECRET'], 'utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    if not hmac.compare_digest(signature, expected_signature):
        return jsonify({'error': 'Invalid signature'}), 400
    
    event = json.loads(payload)
    
    # Handle the event
    if event['type'] == 'booking.created':
        print(f"New booking: {event['data']['object']}")
    elif event['type'] == 'booking.updated':
        print(f"Booking updated: {event['data']['object']}")
    elif event['type'] == 'booking.cancelled':
        print(f"Booking cancelled: {event['data']['object']}")
    else:
        print(f"Unhandled event type: {event['type']}")
    
    return jsonify({'received': True})

if __name__ == '__main__':
    app.run(port=3000)

Php

php
<?php
$webhook_secret = $_ENV['PUNCTUAL_WEBHOOK_SECRET'];
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_PUNCTUAL_SIGNATURE'];

// Verify webhook signature
$expected_signature = hash_hmac('sha256', $payload, $webhook_secret);

if (!hash_equals($signature, $expected_signature)) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$event = json_decode($payload, true);

// Handle the event
switch ($event['type']) {
    case 'booking.created':
        error_log("New booking: " . json_encode($event['data']['object']));
        break;
    case 'booking.updated':
        error_log("Booking updated: " . json_encode($event['data']['object']));
        break;
    case 'booking.cancelled':
        error_log("Booking cancelled: " . json_encode($event['data']['object']));
        break;
    default:
        error_log("Unhandled event type: " . $event['type']);
}

echo json_encode(['received' => true]);
?>

Delivery Status

Understanding webhook delivery states

delivered

Webhook was successfully delivered to your endpoint

failed

Webhook delivery failed and will be retried

pending

Webhook is queued for delivery

retrying

Webhook is being retried after a previous failure

Security & Best Practices

Keep your webhooks secure and reliable

Signature Verification

Always verify webhook signatures to ensure the requests are coming from Punctual. We use HMAC-SHA256 to sign all webhook payloads with your webhook secret.

Idempotency

Webhook events may be delivered multiple times. Use the event ID to ensure you don't process the same event twice.

HTTPS Only

Always use HTTPS endpoints for webhook receivers. We will not deliver webhooks to HTTP endpoints for security reasons.

Response Handling

Respond with a 2xx status code to acknowledge receipt. Any other status code will cause us to retry the webhook delivery.

Ready to get started?

Set up your first webhook and start building powerful integrations