Skip to content
Driive Help Center
Driive Help Center

Webhook Events and Security

This article covers everything you need to know about the data your webhooks deliver and how to ensure that data is authentic. You'll find the full list of available events, an example payload, instructions for verifying HMAC signatures, and details on retry behavior and secret rotation.


Available events

Driive can send webhook notifications for the following events. When creating a webhook, you choose which events to subscribe to.

Appointment events

Event

Fires when

appointment.created

A new appointment is created (via booking page, quick-book, or API)

appointment.updated

Appointment details or status are changed

appointment.confirmed

A time slot is confirmed for the appointment

appointment.cancelled

The appointment is cancelled

appointment.rescheduled

The appointment is moved to a new date and time

Tip: You can subscribe to appointment.* to receive all appointment events with a single subscription. This is useful if you want to keep an external system fully in sync without selecting individual events.


Payload structure

Every webhook delivery sends a JSON payload as an HTTP POST to your destination URL. All payloads follow the same top-level envelope.

Delivery envelope

Field

Type

Description

id

string

Unique delivery ID for deduplication

type

string

The event type (e.g., appointment.created)

data

object

The event-specific data (varies by event type -- see below)

previousData

object

The previous records data before the update. Only for updated events.

createdAt

string

ISO 8601 timestamp of when the event occurred

Appointment event data fields

All appointment.* events include the following fields in the data object:

Field

Type

Description

appointmentId

string

Unique appointment ID

organizationId

string

Your organization ID

appointmentTypeId

string

The appointment type ID

appointmentTypeName

string

The appointment type name (e.g., "Roof Inspection")

status

string

Current appointment status

customer

object

Customer details (see below)

startTime

string or null

Scheduled start time (ISO 8601)

endTime

string or null

Scheduled end time (ISO 8601)

duration

number or null

Duration in minutes

location

object or null

Service address

leadScore

number or null

Lead qualification score (0-100)

bookedAt

string or null

When the appointment was booked

cancelledAt

string or null

When the appointment was cancelled

cancellationReason

string or null

Reason for cancellation

bookingUrl

string

Customer-facing booking link for this appointment

timezone

string or null

Organization timezone

startTimeLocal

string or null

Start time in the organization's local timezone

endTimeLocal

string or null

End time in the organization's local timezone

assignedTeamMembers

array

Team members assigned to this appointment

leadTech

object or null

Primary assigned team member

createdAt

string

When the appointment was created (ISO 8601)

updatedAt

string

When the appointment was last updated (ISO 8601)

Customer object:

Field

Type

Description

firstName

string

Customer's first name

lastName

string

Customer's last name

email

string or null

Email address

phone

string or null

Phone number

street

string or null

Street address

city

string or null

City

state

string or null

State

zip

string or null

ZIP code

Example: appointment.created

{ "id": "del_abc123", "type": "appointment.created", "createdAt": "2025-01-15T10:30:00Z", "data": { "appointmentId": "apt_abc123", "organizationId": "org_xyz789", "appointmentTypeId": "aty_def456", "appointmentTypeName": "Roof Inspection", "status": "needs_scheduled", "customer": { "firstName": "John", "lastName": "Smith", "email": "john@example.com", "phone": "+15551234567", "street": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202" }, "startTime": null, "endTime": null, "duration": 60, "location": { "street": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202", "lat": "39.7392", "lon": "-104.9903" }, "leadScore": 85, "bookedAt": null, "cancelledAt": null, "cancellationReason": null, "bookingUrl": "https://app.driive.com/l/apt_abc123", "timezone": "America/Denver", "startTimeLocal": null, "endTimeLocal": null, "assignedTeamMembers": [], "leadTech": null, "createdAt": "2025-01-15T10:30:00Z", "updatedAt": "2025-01-15T10:30:00Z" } }

Example: appointment.confirmed

{ "id": "del_ghi789", "type": "appointment.confirmed", "createdAt": "2025-01-15T14:00:00Z", "data": { "appointmentId": "apt_abc123", "organizationId": "org_xyz789", "appointmentTypeId": "aty_def456", "appointmentTypeName": "Roof Inspection", "status": "scheduled", "customer": { "firstName": "John", "lastName": "Smith", "email": "john@example.com", "phone": "+15551234567", "street": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202" }, "startTime": "2025-01-20T16:00:00Z", "endTime": "2025-01-20T17:00:00Z", "duration": 60, "location": { "street": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202", "lat": "39.7392", "lon": "-104.9903" }, "leadScore": 85, "bookedAt": "2025-01-15T14:00:00Z", "cancelledAt": null, "cancellationReason": null, "bookingUrl": "https://app.driive.com/l/apt_abc123", "timezone": "America/Denver", "startTimeLocal": "2025-01-20T09:00:00", "endTimeLocal": "2025-01-20T10:00:00", "assignedTeamMembers": [ { "id": "mem_xyz789", "name": "Alex Rivera", "email": "alex@example.com" } ], "leadTech": { "id": "mem_xyz789", "name": "Alex Rivera", "email": "alex@example.com" }, "createdAt": "2025-01-15T10:30:00Z", "updatedAt": "2025-01-15T14:00:00Z" } }

Example: appointment.rescheduled

{ "id": "del_jkl012", "type": "appointment.rescheduled", "createdAt": "2025-01-16T09:00:00Z", "data": { "appointmentId": "apt_abc123", "organizationId": "org_xyz789", "appointmentTypeId": "aty_def456", "appointmentTypeName": "Roof Inspection", "status": "scheduled", "customer": { "firstName": "John", "lastName": "Smith", "email": "john@example.com", "phone": "+15551234567", "street": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202" }, "startTime": "2025-01-22T16:00:00Z", "endTime": "2025-01-22T17:00:00Z", "duration": 60, "location": { "street": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202", "lat": "39.7392", "lon": "-104.9903" }, "leadScore": 85, "bookedAt": "2025-01-15T14:00:00Z", "cancelledAt": null, "cancellationReason": null, "bookingUrl": "https://app.driive.com/l/apt_abc123", "timezone": "America/Denver", "startTimeLocal": "2025-01-22T09:00:00", "endTimeLocal": "2025-01-22T10:00:00", "assignedTeamMembers": [ { "id": "mem_xyz789", "name": "Alex Rivera", "email": "alex@example.com" } ], "leadTech": { "id": "mem_xyz789", "name": "Alex Rivera", "email": "alex@example.com" }, "createdAt": "2025-01-15T10:30:00Z", "updatedAt": "2025-01-16T09:00:00Z" } }

HMAC signature verification

Every webhook delivery includes an X-Driive-Signature header. This header contains an HMAC-SHA256 hash that you should verify to confirm the payload was sent by Driive and hasn't been tampered with.

How it works

  1. Driive takes the raw request body (the JSON payload as a string).

  2. Driive computes an HMAC-SHA256 hash using your webhook's signing secret as the key.

  3. The resulting hash is sent in the X-Driive-Signature header.

  4. Your server performs the same computation and compares the result.

Verification pseudocode

expected = HMAC-SHA256(signing_secret, request_body) actual = request.headers["X-Driive-Signature"] if (expected == actual) { // Payload is authentic — process it } else { // Payload may be forged — reject it }

Verification example (Node.js)

const crypto = require("crypto"); function verifyWebhook(signingSecret, requestBody, signatureHeader) { const expected = crypto .createHmac("sha256", signingSecret) .update(requestBody) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signatureHeader) ); }

Verification example (Python)

import hmac import hashlib def verify_webhook(signing_secret, request_body, signature_header): expected = hmac.new( signing_secret.encode(), request_body.encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header)

Important: Always use a constant-time comparison function (like timingSafeEqual or compare_digest) to prevent timing attacks.

Why verify signatures?

Without verification, anyone who discovers your webhook URL could send fake payloads to your server. Signature verification ensures:

  • The payload was sent by Driive, not a third party

  • The payload hasn't been modified in transit

  • Your integration only processes authentic events


HTTP headers

Every webhook delivery includes the following headers:

Header

Description

Content-Type

application/json

X-Driive-Signature

HMAC-SHA256 signature of the request body

User-Agent

Driive-Webhooks/1.0


Retry logic

If your server doesn't respond with a 2xx status code (200-299), Driive treats the delivery as failed and retries automatically.

Retry behavior:

  • Maximum attempts: 5 retries (6 total deliveries including the original)

  • Backoff strategy: Exponential backoff — each retry waits longer than the previous one

  • Approximate schedule:

    • Retry 1: ~1 minute after failure

    • Retry 2: ~5 minutes

    • Retry 3: ~30 minutes

    • Retry 4: ~2 hours

    • Retry 5: ~8 hours

After all retries are exhausted, the delivery is marked as permanently failed in the delivery log.

Tips for handling retries:

  • Make your webhook endpoint idempotent — processing the same event twice should produce the same result. Use the event timestamp and data.id fields to detect duplicates.

  • Return a 200 status code as quickly as possible. If you need to do heavy processing, accept the webhook, return 200, and process the data asynchronously.

  • Monitor your delivery logs for patterns of failure.


Secret rotation

If your signing secret is compromised (or as part of routine security hygiene), you can rotate it:

  1. Navigate to Developer > Webhooks.

  2. Open the webhook you want to rotate.

  3. Click Rotate Secret.

  4. Driive generates a new signing secret and displays it.

  5. Copy the new secret and update your server's verification logic.

  6. The old secret stops working immediately.

Tip: To avoid downtime during rotation, update your server to accept the new secret before rotating. You can briefly support both secrets during the transition by checking the signature against each one.


Best practices

  • Always verify signatures — Never skip HMAC verification in production

  • Respond quickly — Return 200 within a few seconds; process data asynchronously if needed

  • Handle duplicates — Your endpoint may receive the same event more than once due to retries

  • Log deliveries — Keep your own record of received webhooks for debugging

  • Use HTTPS — Your destination URL must use HTTPS to protect data in transit

  • Monitor delivery health — Check the Driive delivery log regularly and set up your own alerting for failed deliveries


Next steps