For developers

API and webhooks

Webhooks send events from your Kwickly Answers account, like a new booking or a finished call, to a web address you choose. The API lets your own software look things up in your account, and in some cases add a customer or a booking. Both are included on every plan, with no extra charge. You turn them on yourself in your portal.

Two ways in

Webhooks: sent to you

In the portal, under Account, Send to other apps, you add an https web address and pick what to send. When it happens, a short signed message is sent to that address.

The API: you ask

In the portal, under Account, API keys, you make a key. Your software sends it with each request to read bookings, customers, calls and services. A key can only read unless you make it one that can also add things.

Events and API answers carry your customers' names, phone numbers, emails and addresses, and can carry orders, invoice totals, a message someone left and, in the API, a short summary of each call. You choose where they go and who gets a key, and you are responsible for those systems and for any notice or consent your business needs before it sends a customer's details to another service. Webhook events never carry call recordings, written transcripts or card numbers. See the Privacy Policy and Security and trust.

Webhooks

The events

EventSent when
booking.createdA booking is made, by phone, chat, your booking page, you or your team.
booking.changedA booking is moved to another time or person.
booking.canceledA booking is canceled.
contact.createdA customer is added.
call.endedA call ends. Who called, when, how long and how it ended. Never the recording or the transcript. Calls from callers who declined recording, screened sales calls and calls that are not paid for send nothing.
order.createdAn order comes in.
lead.capturedA message or enquiry is left for you. Carries the message they left, up to 1,000 characters.
invoice.sentAn invoice is sent.
estimate.acceptedA customer accepts an estimate.

A change made through the API does not send a webhook, so your own software is not told about what it just did.

The message

Each message is one JSON object. The same event always has the same id, so use it to skip a repeat.

{
  "id": "5b0e5e4a-7a52-4c4c-9a70-0f5a3c0d2f10",
  "type": "booking.created",
  "createdAt": "2026-10-03T16:02:11.000Z",
  "test": false,
  "data": {
    "bookingId": "...",
    "status": "booked",
    "start": "2026-10-20T17:00:00.000Z",
    "end": "2026-10-20T18:00:00.000Z",
    "serviceName": "Gel manicure",
    "customer": { "id": "...", "name": "...", "phone": "...", "email": "..." },
    "address": ""
  }
}

A message sent from the Send a test button has "test": true and made-up details, never a real customer.

Headers

HeaderWhat it holds
X-Kwickly-EventThe event name, like booking.created.
X-Kwickly-Event-IdThe same id as in the message.
X-Kwickly-DeliveryOne send of one message to one address.
X-Kwickly-TimestampWhen this try was signed, as a Unix time.
X-Kwickly-Signaturesha256= and a hex signature (below).

Check the signature

Each web address has its own signing secret, shown once when you add it or make a new one. It starts with whsec_. Take the raw request body, put the timestamp and a full stop in front of it, and sign that with HMAC-SHA256 using the secret. It should equal the hex after sha256=. Compare in constant time, and refuse a timestamp more than five minutes old.

// Node.js
const crypto = require('crypto');
function isFromKwickly(secret, timestamp, rawBody, signatureHeader) {
  const want = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody).digest('hex');
  const a = Buffer.from(want), b = Buffer.from(signatureHeader || '');
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  return fresh && a.length === b.length && crypto.timingSafeEqual(a, b);
}

Some tools that catch a web address, such as spreadsheet and chat automations, don't check a signature. Treat such an address like a password: anyone who has it can send to it.

Where a message can go, and what happens when it fails

  • The address must start with https:// and be reachable from the internet. Addresses on private networks, loopback and link-local ranges are refused, when you save it and again on every send.
  • Redirects are not followed. Use the final address.
  • A send waits 10 seconds for an answer. Any 2xx answer counts as delivered.
  • A message that fails is tried again after 1 minute, 5 minutes, 15 minutes, 1 hour, 4 hours and 12 hours. After 7 tries the message is given up on, and the owner can press Send again in the portal for 7 days.
  • After 10 failures in a row the address is paused, the business owner is emailed and it shows on their dashboard. They fix the address, then press Turn back on. A message waiting on a paused address for 7 days is dropped.
  • An event can arrive more than once, late or out of order, and no event is promised to arrive. Use id to skip repeats, and build for a missed one.
  • The portal's See what was sent shows the last 50 sends of an address: when, what, the result and the number of tries. It never shows message content.
  • You can have up to 5 web addresses.

The API, version 1

The base address is https://portal.kwicklyanswers.com/api/v1. Send your key in a header on every request. The key is checked on every request, so a revoked key stops working on its next request.

curl https://portal.kwicklyanswers.com/api/v1/bookings \
  -H "Authorization: Bearer kw_YOUR_KEY"

What you can read

RequestWhat you get
GET /bookingsUpcoming bookings. Options: from, to (dates like 2026-10-20T17:00:00Z), status=all to include canceled ones, limit (1 to 100, default 50).
GET /bookings/:idOne booking.
GET /contactsCustomers. Options: search, limit.
GET /contacts/:idOne customer. Never the gate or access codes.
GET /callsCalls, newest first: when, how long, the caller's number, how it ended and a short summary. Options: before, limit. Never the recording or the transcript.
GET /servicesYour services and products, with whether each can be booked online.

Answers are JSON, like { "data": [ ... ] }.

What you can add

Only a key made as Look things up, and add customers and bookings can add anything. A key that can only look things up gets a 403.

RequestWhat it does
POST /contactsAdds a customer. Send name, phone, email, address, notes, at least one of the first three. It follows the same rules as adding a customer in the portal, including not adding the same phone number or email twice.
POST /bookingsAdds a booking. Send service (a name from GET /services that can be booked), start (a date and time), name, phone, and email, address, notes when they apply. It goes through the same rules as your booking page: your hours, availability, any question you ask before booking (send "checkAnswer": "yes" if the customer said yes), your towns and your rules for urgent jobs. If a rule says no, nothing is booked and the answer says why. It isn't available to a business that checks every new booking for conflicts.

Errors and limits

  • An error is JSON: { "error": { "code": "...", "message": "..." } }, with the matching HTTP status. Codes include unauthorized, forbidden, not_found, invalid, duplicate, not_booked and rate_limited.
  • Each key can make 600 requests in 10 minutes, and one business 1,200 across all its keys. Adding customers and bookings is limited to 100 an hour per business. Past a limit you get a 429 with a Retry-After header saying how many seconds to wait.
  • A key is shown once when it's made, and only a fingerprint of it is kept, so it can't be shown again. Make a new one if it's lost, and revoke one that might have leaked.
  • Don't use a key to copy a whole account to someone else, to get around a limit, or to harm the service. A key that does can be turned off.

Questions about the API?

Tell me your business name and what you are trying to connect. I'll check what I can.

Email harley@kwicklyanswers.com