Skip to content

Webhooks

A webhook is an address of yours that Tellfox sends a message to when something happens: a new booking, a cancellation, a new customer, a stamp. No polling. An owner adds it on Settings → API → Webhooks with the events it wants (or all of them), for the real business or its test copy. Up to 5 per business.

EventWhen
customer.createdA customer was added
customer.updatedA customer's details changed
customer.erasedA customer was erased on request
customer.note_addedA note was added to a customer
booking.createdA booking was made
booking.movedA booking was moved
booking.assignedA booking was given to someone else
booking.cancelledA booking was cancelled
booking.completedA booking was completed
booking.no_showA customer did not come
booking.reconfirmedA customer said they are coming
class.signupSomeone took a place in a class
class.cancelledA class place was cancelled
session.cancelledA class session was cancelled
package.soldA package was sold
package.voidedA package sale was cancelled
package.used_upA package was used up
loyalty.stamps_givenStamps were given
loyalty.points_givenPoints were given
loyalty.coupon_givenA coupon was given
loyalty.coupon_usedA coupon was used
payment.confirmedA payment was confirmed
ticket_order.paidTickets were bought or registered

Events reach you within about a minute. Each is sent once per webhook; your system may still see one twice if it answered too slowly, so use the event’s id to ignore a repeat.

A POST with a JSON body:

{
"id": "evt_9c0…",
"type": "booking.created",
"created_at": "2026-10-22T09:30:00Z",
"mode": "live",
"business_id": "biz_…",
"data": { "booking": { "id": "bk_…", "status": "confirmed", "start": "2026-10-23T03:00:00Z", "customer_id": "cus_…", "service": { "id": "svc_…", "name": "Massage" } } }
}

data holds the object as the API shows it (booking, customer, package, payment) or, for loyalty, the customer_id and their card. A customer erased on request comes as customer.erased with only the customer_id: delete your copy. The headers:

  • Tellfox-Event: the type
  • Tellfox-Delivery: the event’s id
  • Tellfox-Signature: t=<unix seconds>,v1=<hex>

Answer with any 2xx within 10 seconds. Anything else (or no answer, or a redirect) is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours. The delivery log on Settings → API shows each one for 30 days, with Send again. A webhook that keeps failing for 3 days is switched off and the owners get an email; switch it on again there.

v1 is the HMAC-SHA256 of t, a dot and the raw body, keyed with the webhook’s signing secret (whsec_…, shown once when it was added). Compare in constant time, and refuse a t more than 5 minutes old.

Node.js
import crypto from 'node:crypto';
function fromTellfox(rawBody, header, secret) {
const t = /t=(\d+)/.exec(header)?.[1], v1 = /v1=([0-9a-f]+)/.exec(header)?.[1];
if (!t || !v1 || v1.length !== 64 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
// PHP
function from_tellfox(string $raw, string $header, string $secret): bool {
preg_match('/t=(\d+)/', $header, $t); preg_match('/v1=([0-9a-f]+)/', $header, $v);
if (!$t || !$v || abs(time() - (int) $t[1]) > 300) return false;
return hash_equals(hash_hmac('sha256', $t[1] . '.' . $raw, $secret), $v[1]);
}
# Python
import hmac, hashlib, re, time
def from_tellfox(raw: bytes, header: str, secret: str) -> bool:
t, v1 = re.search(r"t=(\d+)", header), re.search(r"v1=([0-9a-f]+)", header)
if not t or not v1 or abs(time.time() - int(t[1])) > 300: return False
expected = hmac.new(secret.encode(), f"{t[1]}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1[1])
  1. In Make, make a scenario with Webhooks → Custom webhook, add a webhook and copy its address.
  2. In Tellfox, Settings → API → Webhooks → Add a webhook: paste the address, pick A booking was made (booking.created), and add it. Press Send a test so Make learns the shape of the message.
  3. In Make, add Google Sheets → Add a row after the webhook, and map the columns: data.booking.start, data.booking.service.name, data.booking.reference, data.booking.customer_id.
  4. Switch the scenario on. Each new booking adds a row within about a minute.