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
| Event | Sent when |
|---|---|
booking.created | A booking is made, by phone, chat, your booking page, you or your team. |
booking.changed | A booking is moved to another time or person. |
booking.canceled | A booking is canceled. |
contact.created | A customer is added. |
call.ended | A 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.created | An order comes in. |
lead.captured | A message or enquiry is left for you. Carries the message they left, up to 1,000 characters. |
invoice.sent | An invoice is sent. |
estimate.accepted | A 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
| Header | What it holds |
|---|---|
X-Kwickly-Event | The event name, like booking.created. |
X-Kwickly-Event-Id | The same id as in the message. |
X-Kwickly-Delivery | One send of one message to one address. |
X-Kwickly-Timestamp | When this try was signed, as a Unix time. |
X-Kwickly-Signature | sha256= 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
idto 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
| Request | What you get |
|---|---|
GET /bookings | Upcoming 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/:id | One booking. |
GET /contacts | Customers. Options: search, limit. |
GET /contacts/:id | One customer. Never the gate or access codes. |
GET /calls | Calls, 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 /services | Your 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.
| Request | What it does |
|---|---|
POST /contacts | Adds 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 /bookings | Adds 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 includeunauthorized,forbidden,not_found,invalid,duplicate,not_bookedandrate_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-Afterheader 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.