Guide to TIMIFY Webhooks: Features and Best Practices
TIMIFY webhooks tell your systems when something changes in an appointment-scheduling account: a booking is created or moved, a customer is added or deleted, a resource or service changes, or a shift is updated. If you build on TIMIFY's Developer Platform, webhooks save your integration from polling the API for those changes.
This guide covers how TIMIFY webhook subscriptions and scopes work, how to authenticate requests that TIMIFY doesn't sign, and the practices that keep a TIMIFY receiver reliable.
What are TIMIFY webhooks?
TIMIFY webhooks belong to an application in the TIMIFY Developer Platform. Each webhook subscription has a Server URL and a webhook scope, and when a matching event happens, TIMIFY sends an HTTP POST with a JSON body to that URL. An application can have several subscriptions, each with its own URL and scope, so you can send different data to different services.
Requests are not signed. The only built-in protection is an optional custom header: turn on Include header name when you create the subscription, and TIMIFY adds that header, with the secret value you entered, to every request.
TIMIFY webhook features
| Feature | Details |
|---|---|
| Configuration | Developer Platform, your application, Webhooks Subscriptions tab |
| Subscription | A Server URL and a webhook scope. Several subscriptions per application |
| Permissions | The application needs the scopes each webhook requires |
| Method | POST |
| Body | JSON. Field names aren't documented on the overview page |
| Authentication | Optional custom header and secret value, via Include header name |
| Signature | None |
| Sync webhooks | Before Confirmations and Before Reminders expect a synchronous answer |
| Retries, timeout and source IPs | Not documented |
Webhooks and scopes
TIMIFY lists each webhook with its types and the application scopes it needs:
| Webhook | Types | App scopes |
|---|---|---|
| Customers | Created, Updated, Deleted | Customers |
| Event | Created, Updated, Deleted | Events, Customers, Resources, Services |
| Events | Deleted | Events, Customers, Resources, Services |
| Services | Created, Updated, Deleted | Resources, Services |
| Resource | Created, Updated, Deleted | Resources |
| Company | Created, Updated, Deleted | Companies |
| Manager | Created, Updated, Deleted | BM_Managers |
| Enterprise | Updated | Enterprises |
| Customer Fields | Created, Updated, Deleted | Customer fields |
| Shift | Created, Updated, Deleted | Shift, Resources |
| Notification | Created | Notification |
| Service Groups | Created, Updated, Deleted | Service Group |
| Before Confirmations | Sync | Events, Customers, Resources, Services |
| Before Reminders | Sync | Events, Customers, Resources, Services |
These are labels from TIMIFY's overview, not the values sent in the request. TIMIFY's overview doesn't show a payload, so capture a real delivery for each webhook you subscribe to before you rely on its field names.
Setting up TIMIFY webhooks
Pick a header name and a long random value first, for example X-Timify-Secret with the output of openssl rand -hex 32, and store the value server-side.
- Create an application in the TIMIFY Developer Platform, and give it the scopes the webhooks you want need.
- Open the application and select the Webhooks Subscriptions tab.
- Create a webhook subscription with your HTTPS endpoint as the Server URL, and choose its webhook scope.
- Turn on Include header name, and enter your header name and secret value.
With Hookdeck. TIMIFY is a supported source in Hookdeck. Create a source with the TIMIFY source type, enter the API Key Header and the API Key exactly as you set them on the subscription, and use the Hookdeck source URL as the Server URL. Use it for the Created, Updated and Deleted webhooks only: Hookdeck's setup note says that Sync-type webhooks, such as Before Confirmations, expect a synchronous answer and aren't supported. A request without the right header still gets a 200 from Hookdeck, but Hookdeck marks it as failed verification and doesn't deliver it to your service.
Securing TIMIFY webhooks
TIMIFY says to "validate the header name and secret value in your webhook endpoint before processing the payload". Look the header up case-insensitively, compare the whole value in constant time, and check lengths before calling crypto.timingSafeEqual.
const crypto = require('crypto');
const express = require('express');
function safeEqual(a, b) {
const x = Buffer.from(String(a), 'utf8');
const y = Buffer.from(String(b), 'utf8');
return x.length === y.length && crypto.timingSafeEqual(x, y);
}
function verifyTimifyRequest(headers) {
const secret = process.env.TIMIFY_WEBHOOK_SECRET; // the exact header value
if (!secret) return false; // fail closed when unconfigured
const name = (process.env.TIMIFY_WEBHOOK_HEADER || 'X-Timify-Secret').toLowerCase();
const received = headers[name];
return typeof received === 'string' && safeEqual(received, secret);
}
const app = express();
app.post('/webhooks/timify', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyTimifyRequest(req.headers)) {
return res.sendStatus(401);
}
let body;
try {
body = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Log real deliveries to learn the field names, then route on them.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
def verify_timify_request(headers) -> bool:
secret = os.environ.get("TIMIFY_WEBHOOK_SECRET")
if not secret:
return False
name = os.environ.get("TIMIFY_WEBHOOK_HEADER", "X-Timify-Secret")
received = headers.get(name) # case-insensitive in Starlette and Flask
if not received:
return False
return hmac.compare_digest(received.encode("utf-8"), secret.encode("utf-8"))
A static header proves the sender knows the value, but it doesn't prove the body is unchanged, and it gives no replay protection: nothing is signed and there is no timestamp. Serve the endpoint over HTTPS only, use a long random value, keep it out of logs, and change it on the subscription and in your receiver if it might have leaked.
Make TIMIFY webhooks production-ready. Hookdeck Event Gateway checks your header on every TIMIFY request, stores each booking, customer and resource change, and retries delivery to your service when it fails.
TIMIFY webhook limitations and pain points
No signature
The Problem: A custom header is all that separates a real TIMIFY request from a forged one, and it's optional.
Why It Happens: TIMIFY offers Include header name and nothing computed over the body.
Workarounds:
- Always turn on Include header name, with a long random value.
- For important changes, such as a cancelled booking, confirm the current state through TIMIFY's API before acting.
How Hookdeck Can Help: Hookdeck's TIMIFY source compares the header you name with the exact value you enter, and logs every request with its headers and body.
Sync webhooks need a direct endpoint
The Problem: Before Confirmations or Before Reminders behave unexpectedly behind a queue.
Why It Happens: These two webhooks are of type Sync and expect a synchronous answer. A service that acknowledges first and processes later can't give one.
Workarounds:
- Point Sync webhooks straight at a service that answers quickly and synchronously.
- Route only Created, Updated and Deleted webhooks through a queue or gateway.
How Hookdeck Can Help: Use separate subscriptions: Sync webhooks direct to your service, the rest through Hookdeck.
Missing scopes mean missing webhooks
The Problem: A subscription is set up, but some events never arrive.
Why It Happens: Each webhook needs particular application scopes. The Event webhooks, for example, need Events, Customers, Resources and Services.
Workarounds:
- Check the application's scopes against TIMIFY's table before creating the subscription.
How Hookdeck Can Help: Hookdeck's request log shows exactly which webhooks arrive, which makes a missing scope easy to spot.
Undocumented payloads and retries
The Problem: You can't write a parser or plan for failures from the docs alone.
Why It Happens: TIMIFY's webhook overview shows no payload and documents no timeout or retry schedule.
Workarounds:
- Capture a real delivery for each webhook and build your parser from it.
- Return
200quickly, and reconcile through the API after an outage.
How Hookdeck Can Help: Hookdeck stores each request with its body so you can replay it while you develop, and delivers to your service with automatic retries.
Best practices
Split subscriptions by consumer
Because each subscription has its own Server URL and scope, send booking events to your scheduling service and customer events to your CRM through separate subscriptions, each with its own secret.
Make handlers idempotent
Without a documented event ID, key on the record's ID and the change type, and update rather than insert. See our guide to webhook idempotency.
Acknowledge fast and process asynchronously
Check the header, store the request, return 200, then do the work. See why to process webhooks asynchronously.
Conclusion
TIMIFY sends unsigned JSON POST requests to each webhook subscription's Server URL. Turn on Include header name with a long random value, compare it in constant time over HTTPS, keep Sync webhooks on a direct path, and give your application the scopes each webhook needs.
Hookdeck Event Gateway checks the header on every TIMIFY request, records each one, and retries delivery so a booking change isn't lost to an outage on your side.
Get started with Hookdeck for free and handle TIMIFY webhooks reliably in minutes.