# Webhooks API > Receive real-time HTTP notifications when events occur in LinkTime. > Source: https://linktime.io/docs/developers/webhooks > Last updated: February 2026 ## Events Subscribe to any of the following events when creating a webhook: | Event | Description | |-------|-------------| | `booking.created` | A new booking has been confirmed | | `booking.cancelled` | A booking has been cancelled | | `booking.rescheduled` | A booking has been moved to a new time | | `payment.received` | Payment completed for a paid booking | | `contact.created` | A new contact has been added | ## Payload Structure All webhook payloads follow this structure: ```json { "event": "booking.created", "timestamp": "2026-02-07T14:30:00.000Z", "data": { // Event-specific data (see below) } } ``` ### HTTP Headers Each webhook request includes these headers: | Header | Description | |--------|-------------| | `Content-Type` | application/json | | `X-LinkTime-Signature` | HMAC-SHA256 signature for verification | | `X-LinkTime-Event` | The event type (e.g., booking.created) | | `X-LinkTime-Timestamp` | ISO 8601 timestamp of the event | | `User-Agent` | LinkTime-Webhook/1.0 | ## Event Payloads ### `booking.created` ```json { "event": "booking.created", "timestamp": "2026-02-07T14:30:00.000Z", "data": { "id": "clx1234567890", "eventType": { "id": "clx0987654321", "name": "30 Minute Meeting", "slug": "30-min", "duration": 30 }, "startTime": "2026-02-08T10:00:00.000Z", "endTime": "2026-02-08T10:30:00.000Z", "status": "CONFIRMED", "invitee": { "name": "John Smith", "email": "john@example.com", "timezone": "America/New_York" }, "host": { "id": "user_abc123", "name": "Jane Doe", "email": "jane@company.com" }, "meetingLink": "https://meet.google.com/abc-defg-hij", "location": "Google Meet", "notes": "Looking forward to our call!", "isRecurring": false, "recurringGroupId": null } } ``` ### `booking.cancelled` Same as `booking.created`, plus: ```json { "data": { "status": "CANCELLED", "cancelReason": "Schedule conflict" } } ``` ### `booking.rescheduled` Same as `booking.created`, plus previous times: ```json { "data": { "startTime": "2026-02-09T14:00:00.000Z", "endTime": "2026-02-09T14:30:00.000Z", "previousStartTime": "2026-02-08T10:00:00.000Z", "previousEndTime": "2026-02-08T10:30:00.000Z" } } ``` ### `payment.received` ```json { "event": "payment.received", "timestamp": "2026-02-07T14:30:00.000Z", "data": { "bookingId": "clx1234567890", "amount": 50.00, "currency": "USD", "paymentMethod": "stripe", "transactionId": "pi_3ABC123DEF456" } } ``` ### `contact.created` ```json { "event": "contact.created", "timestamp": "2026-02-07T14:30:00.000Z", "data": { "id": "clx1234567890", "name": "John Smith", "email": "john@example.com", "phone": "+14155551234", "company": "Acme Inc", "notes": null, "createdAt": "2026-02-07T14:30:00.000Z", "source": "booking" } } ``` ## Signature Verification Each webhook includes an HMAC-SHA256 signature in the `X-LinkTime-Signature` header. Always verify this signature to ensure the webhook came from LinkTime. > **Security:** Never process webhooks without verifying the signature. Your webhook secret is shown once when you create the webhook - store it securely. ### Verification Steps 1. Get the raw request body (as a string, not parsed JSON) 2. Compute HMAC-SHA256 of the body using your webhook secret 3. Compare with the signature header (use timing-safe comparison) ### Example: Node.js ```typescript import crypto from 'crypto'; function verifyWebhook(payload: string, signature: string, secret: string): boolean { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex'); // Timing-safe comparison return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // In your webhook handler: app.post('/webhooks/linktime', (req, res) => { const signature = req.headers['x-linktime-signature']; const isValid = verifyWebhook(req.rawBody, signature, process.env.LINKTIME_WEBHOOK_SECRET); if (!isValid) { return res.status(401).send('Invalid signature'); } const event = req.body; // Process the webhook... res.status(200).send('OK'); }); ``` ### Example: Python ```python import hmac import hashlib def verify_webhook(payload: bytes, signature: str, secret: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) # In your Flask handler: @app.route('/webhooks/linktime', methods=['POST']) def handle_webhook(): signature = request.headers.get('X-LinkTime-Signature') is_valid = verify_webhook(request.data, signature, WEBHOOK_SECRET) if not is_valid: return 'Invalid signature', 401 event = request.json # Process the webhook... return 'OK', 200 ``` ## Retry Behavior LinkTime automatically retries failed webhook deliveries to ensure reliability. We use a two-phase retry strategy: ### Phase 1: Immediate Retries During the initial request: - Attempt 1: Immediate - Attempt 2: After 10 seconds - Attempt 3: After 60 seconds ### Phase 2: Background Retries Via scheduled job: - Attempt 4: After 1 hour - Attempt 5: After 4 hours - Attempt 6: After 24 hours ### What Gets Retried | Response | Retried? | Reason | |----------|----------|--------| | 2xx Success | No | Delivery successful | | 4xx Client Error | No | Configuration issue - fix your endpoint | | 429 Rate Limited | Yes | Temporary - will retry later | | 5xx Server Error | Yes | Temporary - will retry later | | Timeout (30s) | Yes | Endpoint slow - will retry later | | Network Error | Yes | DNS/connection issue - will retry later | > **Tip:** Return a 2xx status code as quickly as possible. Process the webhook asynchronously if needed. This prevents timeouts and unnecessary retries. ## Best Practices 1. **Always verify signatures** — Never trust webhooks without verifying the HMAC signature. 2. **Return 200 quickly** — Acknowledge receipt immediately, then process asynchronously. We timeout after 30 seconds. 3. **Handle duplicates** — Due to retries, you may receive the same webhook multiple times. Use the booking ID to deduplicate. 4. **Use HTTPS** — Always use HTTPS endpoints. We do not send webhooks to HTTP URLs in production. 5. **Monitor delivery status** — Check the webhook delivery history in Settings > Webhooks to debug issues. ## Integration Guides - [Make.com](https://linktime.io/docs/automation/make) — Connect to 1000+ apps with Make.com - [Manage Webhooks](https://linktime.io/dashboard/settings/webhooks) — Create and test your webhooks