Digital Front Desk Try it free
Developers

Webhooks and the integrations API

Digital Front Desk can tell your own systems what happens at your front desk as it happens: a visitor checking in, a call being summarised, an invoice being paid. Add an https address in Settings, under "Webhooks and Zapier", and we POST a small signed JSON message to it for each event you choose. Or make an API key there and connect through Zapier.

What we send

Every message is one JSON object, version 1:

{
  "id": "evt_3f9a0c...",          // unique per event; the same on every retry
  "type": "visitor.checked_in",
  "version": 1,
  "created_at": "2026-10-09T14:03:11Z",
  "tenant": "your-account",        // your account's short name
  "data": { ... }                  // the fields listed for each event below
}

Headers on every request:

HeaderMeaning
X-DFD-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256> — see verifying.
X-DFD-Event-IdThe event's id. Use it to ignore a message you already processed.
X-DFD-Event-TypeThe event's type.
X-DFD-Delivery-IdThis delivery, for when you write to us about one.

Privacy by default. We never send a visitor's reason for visiting or their answers to your questions, call transcripts or recordings, the answers to your forms, notes, health details or payment card data. A visitor's name is included only if your arrival alerts may name visitors and you keep visitor details (Kiosk settings → Notifications and Privacy); otherwise visitor_name is null, and your Webhooks settings page says which of the two is keeping it out. Call summaries are sent only to an address you tick "Include call summaries" for. Fields we add later are additive: ignore what you do not recognise.

Events

An event is offered only while the part of the product it comes from is switched on for your account. webhook.test is sent by the "Send test event" button and carries only a message.

visitor.checked_in — Somebody finished check-in on your lobby screen.

Field in dataMeaning
visit_idOur id for this visit.
statuschecked_in, checked_out or cancelled.
checked_in_atWhen they checked in (ISO 8601, UTC).
checked_out_atWhen they left, or null.
badge_numberThe badge number printed or shown, if any.
host_staff_idThe staff member they asked for, or null.
host_nameThat staff member's name as it appears in your staff list.
visitor_nameOnly if your arrival alerts may name visitors and you keep visitor details; otherwise null.

visitor.checked_out — A visit was closed at the desk, from a staff member's phone link, or by a desk sweep. Visits closed automatically overnight do not fire it.

Field in dataMeaning
visit_idOur id for this visit.
statuschecked_in, checked_out or cancelled.
checked_in_atWhen they checked in (ISO 8601, UTC).
checked_out_atWhen they left, or null.
badge_numberThe badge number printed or shown, if any.
host_staff_idThe staff member they asked for, or null.
host_nameThat staff member's name as it appears in your staff list.
visitor_nameOnly if your arrival alerts may name visitors and you keep visitor details; otherwise null.
checked_out_bydesk, phone (a staff member's link) or sweep.

visitor.claimed — A member of staff pressed "I'll take this" on an arrival.

Field in dataMeaning
visit_idOur id for this visit.
statuschecked_in, checked_out or cancelled.
checked_in_atWhen they checked in (ISO 8601, UTC).
checked_out_atWhen they left, or null.
badge_numberThe badge number printed or shown, if any.
host_staff_idThe staff member they asked for, or null.
host_nameThat staff member's name as it appears in your staff list.
visitor_nameOnly if your arrival alerts may name visitors and you keep visitor details; otherwise null.
claimed_atWhen they claimed it.
claimed_by_staff_idWho claimed it, or null for a login with no staff row.
claimed_by_nameTheir name as your staff list shows it.

staff.signed_in — A member of staff signed in at the door.

Field in dataMeaning
presence_idOur id for this sign-in.
staff_idThe staff member.
staff_nameTheir name as your staff list shows it.
signed_in_atWhen.
sourceHow: phone or dashboard.

staff.signed_out — A member of staff signed out, or the desk signed them out.

Field in dataMeaning
presence_idOur id for this sign-in.
staff_idThe staff member.
staff_nameTheir name as your staff list shows it.
signed_in_atWhen they signed in.
signed_out_atWhen they left.
sourceHow it ended: phone or dashboard.

call.completed — A phone call ended and its summary is ready. No transcript, ever.

Field in dataMeaning
call_idOur id for this call.
directioninbound or outbound.
statusHow it ended, e.g. completed, voicemail, transferred.
from_numberThe caller's number.
to_numberThe number they rang.
started_atWhen it started.
ended_atWhen it ended.
duration_secondsLength in seconds.
summaryThe AI summary — ONLY if you ticked "Include call summaries" for this address; otherwise absent.

contact.created — A new client record was created — by a call, a check-in with consent, a form or a person. Spreadsheet imports do not fire it.

Field in dataMeaning
contact_idOur id for the client.
nameTheir name.
emailTheir email, if known.
phoneTheir phone, if known.
companyTheir company, if known.
sourceWhere they came from: call, visit, form, manual ...
created_atWhen.

appointment.booked — An appointment or booking request was made here (not one mirrored from Google Calendar).

Field in dataMeaning
appointment_idOur id.
contact_idThe client, or null.
starts_atWhen it starts (UTC).
minutesHow long.
statusrequested or confirmed.
assigned_toWho it is with, as typed.
titleThe appointment's title.

appointment.cancelled — An appointment was cancelled. The reason is not sent.

Field in dataMeaning
appointment_idOur id.
contact_idThe client, or null.
starts_atWhen it was to start (UTC).
minutesHow long.
statusAlways cancelled.
assigned_toWho it was with.
titleThe appointment's title.

form.submitted — Somebody submitted one of your forms. The answers are NOT sent — open the submission in Digital Front Desk.

Field in dataMeaning
submission_idOur id.
form_idWhich form.
form_nameThe form's name.
sourcepublic, link or kiosk.
submitted_atWhen.

quote.accepted — Your customer accepted a quote.

Field in dataMeaning
quote_idOur id.
numberThe quote number.
total_centsTotal in cents.
currencye.g. CAD.
deal_idThe job it belongs to.
contact_idThe client.
accepted_atWhen.
accepted_vialink or staff.

invoice.sent — An invoice was issued (numbered and made a link).

Field in dataMeaning
invoice_idOur id.
numberThe invoice number.
total_centsTotal in cents.
paid_centsPaid so far.
currencye.g. CAD.
issue_dateIssue date.
due_dateDue date.
deal_idThe job.
contact_idThe client.
sent_atWhen.

invoice.paid — The balance reached zero. Card details are never sent.

Field in dataMeaning
invoice_idOur id.
numberThe invoice number.
total_centsTotal in cents.
paid_centsPaid so far.
currencye.g. CAD.
issue_dateIssue date.
due_dateDue date.
deal_idThe job.
contact_idThe client.
paid_atWhen.

Verifying the signature

Each address has its own signing secret (whsec_…), shown once when you add it. The signature is HMAC-SHA256, keyed with the secret, over the timestamp, a full stop, and the exact request body. Reject a message whose signature does not match, or whose timestamp is more than 5 minutes from your clock — that window is what stops somebody replaying a message they captured.

Python

import hashlib, hmac, time

def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
    fields = dict(part.split("=", 1) for part in header.split(","))
    timestamp = int(fields["t"])
    if abs(time.time() - timestamp) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body,
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, fields.get("v1", ""))

# Flask:  verify(SECRET, request.headers["X-DFD-Signature"], request.get_data())

Node.js

const crypto = require("crypto");

function verify(secret, header, rawBody, tolerance = 300) {
  const fields = Object.fromEntries(header.split(",").map(p => p.split("=", 2)));
  const timestamp = Number(fields.t);
  if (Math.abs(Date.now() / 1000 - timestamp) > tolerance) return false;
  const expected = crypto.createHmac("sha256", secret)
    .update(`${timestamp}.`).update(rawBody).digest("hex");
  const given = Buffer.from(fields.v1 || "", "hex");
  const want = Buffer.from(expected, "hex");
  return given.length === want.length && crypto.timingSafeEqual(given, want);
}

// Express: app.post("/dfd", express.raw({ type: "application/json" }), (req, res) => {
//   if (!verify(SECRET, req.get("X-DFD-Signature"), req.body)) return res.sendStatus(400);
//   res.sendStatus(200);
// });

Verify against the raw bytes you received, before parsing the JSON — re-serialising changes the bytes and the signature with them.

Delivery and retries

The integrations API (Zapier and REST hooks)

Make an API key in Settings, under "Webhooks and Zapier", and send it as X-API-Key: dfd_k_… (or Authorization: Bearer dfd_k_…). A key belongs to one account and is shown once; revoking it also removes every subscription it made.

CallWhat it does
GET /api/v1/meChecks the key; returns your account's name and the events available to it.
POST /api/v1/hooksBody {"event": "visitor.checked_in", "target_url": "https://…"}. Subscribes the address to one event; returns its id. Messages are the same signed messages described above.
DELETE /api/v1/hooks/{id}Unsubscribes.
GET /api/v1/hooksThe subscriptions this key made.
GET /api/v1/events/{type}/samplesUp to three recent messages of that event (an example if there are none yet) — what Zapier shows while you build a Zap.

Requests are limited to 60 a minute per address.