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 |
|---|---|
| A new appointment is created (via booking page, quick-book, or API) |
| Appointment details or status are changed |
| A time slot is confirmed for the appointment |
| The appointment is cancelled |
| 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 |
|---|---|---|
| string | Unique delivery ID for deduplication |
| string | The event type (e.g., |
| object | The event-specific data (varies by event type -- see below) |
| object | The previous records data before the update. Only for |
| 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 |
|---|---|---|
| string | Unique appointment ID |
| string | Your organization ID |
| string | The appointment type ID |
| string | The appointment type name (e.g., "Roof Inspection") |
| string | Current appointment status |
| object | Customer details (see below) |
| string or null | Scheduled start time (ISO 8601) |
| string or null | Scheduled end time (ISO 8601) |
| number or null | Duration in minutes |
| object or null | Service address |
| number or null | Lead qualification score (0-100) |
| string or null | When the appointment was booked |
| string or null | When the appointment was cancelled |
| string or null | Reason for cancellation |
| string | Customer-facing booking link for this appointment |
| string or null | Organization timezone |
| string or null | Start time in the organization's local timezone |
| string or null | End time in the organization's local timezone |
| array | Team members assigned to this appointment |
| object or null | Primary assigned team member |
| string | When the appointment was created (ISO 8601) |
| string | When the appointment was last updated (ISO 8601) |
Customer object:
Field | Type | Description |
|---|---|---|
| string | Customer's first name |
| string | Customer's last name |
| string or null | Email address |
| string or null | Phone number |
| string or null | Street address |
| string or null | City |
| string or null | State |
| 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
Driive takes the raw request body (the JSON payload as a string).
Driive computes an HMAC-SHA256 hash using your webhook's signing secret as the key.
The resulting hash is sent in the
X-Driive-Signatureheader.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
timingSafeEqualorcompare_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 |
|---|---|
|
|
| HMAC-SHA256 signature of the request body |
|
|
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
timestampanddata.idfields to detect duplicates.Return a
200status code as quickly as possible. If you need to do heavy processing, accept the webhook, return200, 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:
Navigate to Developer > Webhooks.
Open the webhook you want to rotate.
Click Rotate Secret.
Driive generates a new signing secret and displays it.
Copy the new secret and update your server's verification logic.
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
200within a few seconds; process data asynchronously if neededHandle 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
Webhooks — Set up and manage your webhook subscriptions
API Keys — Create API keys for programmatic access
Troubleshooting — Fix common webhook issues