Booking webhooks
Get a signed HTTPS request when a booking is created, confirmed, cancelled, rescheduled, completed or marked no-show. Payloads, signature checks and retries.
Booking webhooks tell your server the moment a booking changes, whether it was made on your website, through the API, on a Fiko Page, in a chat with the AI agent or by the business in the dashboard. Use them to update your CRM or ERP, send your own SMS, sync another calendar or trigger fulfilment.
Add an endpoint
- In Fiko open Settings → API & Developers → Website booking & webhooks → Add endpoint.
- Enter an
https://URL on your server and pick the events you want (none picked means all). - Copy the signing secret (
whsec_...) into your server's configuration. - Press Send test to deliver a signed
pingand see your server's answer in the delivery log.
A business can have up to 5 endpoints. Each one can be paused, and its secret shown again from the dashboard.
Events
| Event | When |
|---|---|
booking.created | A booking was made. status is pending (waiting for approval) or confirmed. |
booking.confirmed | A pending booking was approved. |
booking.rescheduled | The start time changed. |
booking.cancelled | The customer or the business cancelled. |
booking.completed | The appointment was marked done. |
booking.no_show | The customer was marked as not showing up. |
ping | Sent by the Send test button only. |
New event types may be added; ignore the ones you don't know.
Request
Fiko sends a POST with a JSON body:
POST /webhooks/fiko HTTP/1.1
Content-Type: application/json
User-Agent: Fiko-Webhooks/1.0 (+https://fiko.io/developers)
Fiko-Event-Id: evt_9c1d0b2e4f6a8c0e2a4b6d8f
Fiko-Event-Type: booking.created
Fiko-Signature: t=1795012345,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"id": "evt_9c1d0b2e4f6a8c0e2a4b6d8f",
"object": "event",
"type": "booking.created",
"api_version": "2026-11-01",
"created": "2026-11-20T18:04:12.441208+00:00",
"data": {
"object": {
"id": "a1b2c3d4-...",
"object": "reservation",
"confirmation_code": "K7Q2MD",
"status": "pending",
"service": { "id": "3f2a6c1e-...", "name": "Haircut" },
"resource": { "id": "8d41e0a2-...", "name": "Elif" },
"start": "2026-12-02T07:00:00+00:00",
"end": "2026-12-02T07:45:00+00:00",
"local_date": "2026-12-02",
"local_time": "10:00",
"timezone": "Europe/Istanbul",
"business": { "name": "Atelier Hair" },
"source": "website",
"can_cancel": true,
"manage_url": "https://app.fiko.net/booking/manage/...",
"customer": { "name": "Ayşe Yılmaz", "phone": "+905551234567", "email": "ayse@example.com" },
"notes": "First visit",
"metadata": { "order_id": "1042" }
}
}
}
data.object is the full reservation as it is when the event is sent, including customer details and your metadata.
Verify the signature
Every request is signed with your endpoint's secret, so you can reject anything that did not come from Fiko. The Fiko-Signature header holds a Unix timestamp t and v1, the hex HMAC-SHA256 of "<t>.<raw request body>".
- Split the header into
tandv1. - Compute HMAC-SHA256 of
t + "." + bodywith your secret, using the raw body bytes, before any JSON parsing. - Compare it to
v1with a constant-time comparison. - Reject timestamps more than 5 minutes old, so a captured request cannot be replayed later.
// Express. Use the raw body: re-serialised JSON will not match.
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/webhooks/fiko", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Fiko-Signature") || "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto
.createHmac("sha256", process.env.FIKO_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
const valid =
parts.v1?.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
if (!fresh || !valid) return res.sendStatus(400);
const event = JSON.parse(req.body);
// Answer quickly, then do the work in the background.
res.sendStatus(200);
handle(event);
});
Respond and retry
Answer with any 2xx status within 10 seconds. Do slow work (emails, other APIs) after responding, in a queue or background job.
Anything else counts as a failure: other statuses, timeouts, connection errors and redirects (Fiko does not follow them, so use the final URL). Failed deliveries are retried after about 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, roughly 20 hours in all, and are then marked failed. You can send any delivery again from the log.
If 15 events in a row fail, the endpoint is turned off and the business sees why in the dashboard. Turning it back on resets the count.
Handle events safely
- Duplicates. A retry can arrive after your server already handled the first attempt (for example when it timed out). Store
Fiko-Event-Id(alsoidin the body) and skip events you have seen. - Order. Events can arrive out of order, especially after retries. Use
data.object.statusandstartas the current truth, or fetch the reservation with the secret key when order matters. - Unknown fields and events. New fields and event types are added over time. Ignore what you don't recognise.
- Personal data. Payloads include customer names, phone numbers and emails. Store and log them according to your privacy obligations.
Network
Fiko only sends to public https:// addresses; private, local and internal network addresses are refused. Requests come from Fiko's servers on AWS. Verify the signature rather than allowing IP addresses, since the addresses can change.
Questions or something missing? Write to support@fiko.net.