Webhooks API

Receive real-time HTTP notifications when events occur in LinkTime.

Events

Subscribe to any of the following events when creating a webhook:

EventDescription
booking.createdA new booking has been confirmed
booking.cancelledA booking has been cancelled
booking.rescheduledA booking has been moved to a new time
payment.receivedPayment completed for a paid booking
contact.createdA new contact has been added

Payload Structure

All webhook payloads follow this structure:

{
  "event": "booking.created",
  "timestamp": "2026-02-07T14:30:00.000Z",
  "data": {
    // Event-specific data (see below)
  }
}

HTTP Headers

Each webhook request includes these headers:

HeaderDescription
Content-Typeapplication/json
X-LinkTime-SignatureHMAC-SHA256 signature for verification
X-LinkTime-EventThe event type (e.g., booking.created)
X-LinkTime-TimestampISO 8601 timestamp of the event
User-AgentLinkTime-Webhook/1.0

Event Payloads

booking.created

{
  "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": "[email protected]",
      "timezone": "America/New_York"
    },
    "host": {
      "id": "user_abc123",
      "name": "Jane Doe",
      "email": "[email protected]"
    },
    "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:

{
  "data": {
    // ... all booking fields ...
    "status": "CANCELLED",
    "cancelReason": "Schedule conflict"
  }
}

booking.rescheduled

Same as booking.created, plus previous times:

{
  "data": {
    // ... all booking fields with NEW times ...
    "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

{
  "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

{
  "event": "contact.created",
  "timestamp": "2026-02-07T14:30:00.000Z",
  "data": {
    "id": "clx1234567890",
    "name": "John Smith",
    "email": "[email protected]",
    "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

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

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

ResponseRetried?Reason
2xx SuccessNoDelivery successful
4xx Client ErrorNoConfiguration issue - fix your endpoint
429 Rate LimitedYesTemporary - will retry later
5xx Server ErrorYesTemporary - will retry later
Timeout (30s)YesEndpoint slow - will retry later
Network ErrorYesDNS/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