Fiko Developers

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

  1. In Fiko open Settings → API & Developers → Website booking & webhooks → Add endpoint.
  2. Enter an https:// URL on your server and pick the events you want (none picked means all).
  3. Copy the signing secret (whsec_...) into your server's configuration.
  4. Press Send test to deliver a signed ping and 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

EventWhen
booking.createdA booking was made. status is pending (waiting for approval) or confirmed.
booking.confirmedA pending booking was approved.
booking.rescheduledThe start time changed.
booking.cancelledThe customer or the business cancelled.
booking.completedThe appointment was marked done.
booking.no_showThe customer was marked as not showing up.
pingSent 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:

HTTP
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
JSON
{
  "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>".

  1. Split the header into t and v1.
  2. Compute HMAC-SHA256 of t + "." + body with your secret, using the raw body bytes, before any JSON parsing.
  3. Compare it to v1 with a constant-time comparison.
  4. Reject timestamps more than 5 minutes old, so a captured request cannot be replayed later.
JavaScript
// 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 (also id in the body) and skip events you have seen.
  • Order. Events can arrive out of order, especially after retries. Use data.object.status and start as 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.