Skip to content
Get API key

Concepts

Webhooks

Instead of polling, let Sansflow call you. When an order, booking or reservation changes, Sansflow POSTs a signed JSON event to your HTTPS endpoint within about a minute.

Setting up an endpoint

  1. Add the endpoint in Sansflow An owner or manager opens Developers → Webhooks, enters your HTTPS URL and ticks the events it should receive.
  2. Copy the signing secret It is shown once, at creation. Store it on your server as SANSFLOW_WEBHOOK_SECRET.
  3. Verify every delivery Check the signature before you trust the body (below), answer 2xx within 5 seconds, and do the slow work afterwards.

The payload

Every delivery is a POST with a JSON body of three fields: event (the event name), delivered_at (the time of this attempt) and data (the object, in the same shape the API returns it).

A delivery
POST /webhooks/sansflow HTTP/1.1
Content-Type: application/json
X-Sansflow-Event: booking.requested
X-Sansflow-Signature: sha256=5d41402abc4b2a76b9719d911017c592…
X-Sansflow-Delivery: 9a3c…
X-Sansflow-Attempt: 1

{
  "event": "booking.requested",
  "delivered_at": "2026-09-18T03:12:51+00:00",
  "data": {
    "id": "…",
    "status": "awaiting_confirmation",
    "short_code": "7K2QM4X9PD3R",
    "customer_name": "Budi",
    "party_size": 4,
    "requested_at": "2026-09-20T19:00:00+00:00",
    "location_id": "…"
  }
}
HeaderMeaning
X-Sansflow-EventThe event name, same as event in the body.
X-Sansflow-Signaturesha256= followed by the hex HMAC-SHA256 of the raw body, keyed with your endpoint's secret.
X-Sansflow-DeliveryA unique id for this delivery. De-duplicate on it.
X-Sansflow-Attempt1 for the first try, then counting up on retries.
X-Sansflow-Test1 on a delivery sent with the Send test button.

Verifying the signature

Compute the HMAC over the raw request body, before any JSON parsing, and compare it to the header in constant time. Reject anything that doesn't match.

Verify a delivery
import crypto from "node:crypto";
import express from "express";

const app = express();

// Verify against the RAW body: parsing and re-serialising JSON changes the bytes.
app.post("/webhooks/sansflow", express.raw({ type: "*/*" }), (req, res) => {
  const raw = req.body; // Buffer
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.SANSFLOW_WEBHOOK_SECRET)
    .update(raw)
    .digest("hex");
  const got = req.get("X-Sansflow-Signature") || "";
  const ok = got.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
  if (!ok) return res.status(401).send("invalid signature");

  const evt = JSON.parse(raw.toString("utf8"));
  // De-duplicate on X-Sansflow-Delivery, then hand off to a queue and answer fast.
  console.log(req.get("X-Sansflow-Delivery"), evt.event, evt.data.id);
  res.sendStatus(200);
});

Retries and delivery

  • Answer with any 2xx within 5 seconds. Anything else, or no answer, is a failed attempt.
  • Failed attempts are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours.
  • After 8 consecutive failed deliveries the endpoint is switched off until someone switches it back on in Sansflow.
  • Events are queued in the same transaction as the change and sent every minute, so expect up to about 60 seconds of delay.
  • Each attempt is one POST, but a retry after a timeout can reach you after the first try did. Always de-duplicate on X-Sansflow-Delivery.
  • Order isn't guaranteed across retries. Use the object's own status and timestamps, not arrival order, as the truth.

Events

FamilyEventsFires for
order.*pending_confirmation, accepted, ready, completed, cancelledOrders created through the API change state.
booking.*requested, confirmed, rejected, expiredAny booking request of the business, whoever made or decided it.
reservation.*created, updated, seated, completed, cancelled, no_showAny reservation, from a confirmed booking or typed in by staff.

A confirmed booking sends reservation.created then booking.confirmed, from the same transaction. Every event and its data is in the events reference.

Testing

Test orders made with an sf_test_ key never send webhooks. To test your endpoint, use Send test on the endpoint in Sansflow: it posts a signed order.accepted sample in the real shape, with X-Sansflow-Test: 1.

Stuck? Quote the request_id from the response and write toSansflow support.Checked against API v1 on .