If you run a business that relies on bookings, you probably need your booking data in more than one place. Your CRM needs to know when a new customer books. Your ERP needs the revenue data. Your marketing tool needs to trigger a welcome email. Your custom app needs to create bookings programmatically.
Cowlendar's Public API and Webhooks make all of this possible. The API lets you read your services and bookings, and create new bookings from any external system. Webhooks push real-time notifications to your server every time something happens: a booking is created, confirmed, cancelled, rescheduled, or a subscription event occurs.
Both features are currently in Beta.
What can you build with the Cowlendar API?
Here are real integration scenarios that Cowlendar merchants are building right now:
Sync every booking to HubSpot, Salesforce, or any CRM. When a customer books a haircut, a yoga class, or a rental, a webhook fires instantly with the full booking data (name, email, phone, service, date, price, team member). Your CRM creates or updates the contact automatically, with no manual data entry.
Build your own booking page or mobile app. Use the API to fetch your services, including durations, team members, equipment, and participant categories, and create bookings from your own frontend. The booking triggers the same confirmation emails, SMS notifications, and Google Calendar sync as any booking made through the Cowlendar widget.
Automate workflows with Zapier or Make without code. Point a Cowlendar webhook to your Zapier or Make webhook URL. Every booking event becomes a trigger. Send a Slack notification when someone books, add a row to Google Sheets, create a Trello card, or trigger an email sequence in Klaviyo or Mailchimp.
Feed booking data into your reporting or BI tool. Use the List Bookings endpoint to pull all bookings within a date range, filtered by status, service, attendance, or customer email. Export to your data warehouse, Google Sheets, or Airtable for custom dashboards.
Link bookings to subscription credits. When creating a booking via API, pass a subscription_id to automatically deduct one credit from the customer's plan, for example a 10-class yoga pass.
Connect POS or admin bookings to external systems. Webhooks fire for all booking sources: the storefront widget, the admin panel, POS, and the API itself. Nothing falls through the cracks.
Authentication
Every API request requires a Bearer token generated from your Cowlendar admin. Tokens have full read and write access to your shop's bookings and services. Treat them like passwords: do not commit them to version control or share them in public channels.
How to generate your API token
Step 1: In your Shopify admin, open the Cowlendar app.
Step 2: Go to Settings > Public API.
Step 3: Click Generate token.
Step 4: Give your token a descriptive name so you can identify it later, for example "HubSpot sync", "Zapier", or "Mobile app".
Step 5: Click Generate. Cowlendar shows you the full token one time only. Copy it immediately and store it in a secure location like a password manager or your server's environment variables.
You can revoke any token at any time from the same page. Revoking a token immediately stops all API requests using it.
Using your token
Include the token in the Authorization header of every API request:
Authorization: Bearer YOUR_TOKEN_HERE
Example with curl:
curl https://app.cowlendar.com/public-api/v1/services -H "Authorization: Bearer YOUR_TOKEN_HERE"
If the token is missing or invalid, the API returns 401.
API endpoints
Base URL: https://app.cowlendar.com
List services
GET /public-api/v1/services
Returns all non-archived services in your shop. Use this endpoint to discover your service IDs, types, durations, team members, equipment, and participant categories before creating bookings via the API.
Query parameters:
limit (optional, 1-100): number of results per page.
cursor (optional): pagination cursor from a previous response.
is_active (optional, true or false): filter by active or inactive services.
type (optional): filter by service type. Values: classic-checkout, classic-no-checkout, multiday-checkout, multiday-no-checkout, multislot-checkout, multislot-no-checkout, fullday-checkout, fullday-no-checkout.
q (optional, max 120 characters): search services by title.
sort (optional): -id (default, newest first), id (oldest first), title (A to Z), -title (Z to A).
Example request:
curl "https://app.cowlendar.com/public-api/v1/services?is_active=true&limit=10" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Example response (shortened):
{
"data": [
{
"id": "6789abcdef0123456789abcd",
"title": "Haircut",
"type": "classic-checkout",
"is_active": true,
"timezone": "Europe/Paris",
"durations": [30, 45, 60],
"default_duration": 30,
"avs_type": "teammates",
"meeting_location": "in_person",
"enable_participants": false,
"participants": [],
"equipments": [],
"teammates": [
{
"id": "aaa111bbb222ccc333ddd444",
"firstname": "Marie",
"lastname": "Dupont",
"email": "[email protected]",
"thumbnail": null
}
],
"product": {
"handle": "haircut",
"title": "Haircut",
"image": "https://cdn.shopify.com/..."
},
"created_at": "2025-09-01T10:00:00.000Z",
"updated_at": "2026-05-10T08:30:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
}
Understanding key service fields
avs_type tells you how this service manages availability and determines whether you need to pass a teammate_id when creating bookings:
avs_type: "teammates" means each team member has their own independent schedule. When creating a booking, pass a teammate_id from the teammates array. If you omit it, the shop owner is used as fallback.
avs_type: "service" means the service itself owns its availability. The assigned team member is auto-determined from the booking time. You can still pass a teammate_id to force-assign a specific person.
avs_type: "equipment" means availability is per-equipment. Similar to service mode, but tied to equipment capacity.
enable_participants tells you whether the service uses typed participant categories like "Adult", "Child", or "Senior". When true, the participants array contains the categories with their IDs, min and max quantities, and defaults. You need these IDs when creating bookings with a detailed participant breakdown.
equipments contains bookable resources such as rooms, courts, vehicles, or bikes with their variants and SKUs. For equipment with tracking_type: "same", all units are interchangeable and you can use any SKU, typically base. For tracking_type: "unique", each unit has its own SKU and you must pick from variants[].sku.
type tells you the booking model. The suffix -checkout or -no-checkout indicates whether the service uses Shopify checkout. The prefix indicates the scheduling model: classic for a single time slot, multiday for multiple days such as hotel stays, multislot for multiple consecutive slots, and fullday for an entire-day booking.
List bookings
GET /public-api/v1/bookings
Returns bookings for your shop. This is the endpoint you use for CRM syncs, reporting exports, and custom booking dashboards.
Query parameters:
limit (optional, 1-100): results per page.
cursor (optional): pagination cursor.
start (optional, ISO 8601 datetime): only bookings starting on or after this date.
end (optional, ISO 8601 datetime): only bookings starting before this date.
service_id (optional, array of IDs): filter by one or more services.
subscription_id (optional, array of IDs): filter by subscriptions.
status (optional, array): confirmed, pending, declined, canceled.
attendance (optional, array): booked, pending, arrived, started, completed, no-show, delayed, paid.
customer_email (optional): filter by exact customer email.
sort (optional): -id (default, newest first), id (oldest first), start_date (earliest first), -start_date (latest first).
Example: get all confirmed bookings for next week:
curl "https://app.cowlendar.com/public-api/v1/bookings?status=confirmed&start=2026-05-25T00:00:00Z&end=2026-06-01T00:00:00Z&sort=start_date" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Example: find all bookings for a specific customer:
curl "https://app.cowlendar.com/public-api/v1/[email protected]" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Example: export all no-shows from a specific service:
curl "https://app.cowlendar.com/public-api/v1/bookings?attendance=no-show&service_id=6789abcdef0123456789abcd" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Example response (shortened):
{
"data": [
{
"id": "abc123def456ghi789jkl012",
"booking_str": "COW-1234",
"service": {
"id": "6789abcdef0123456789abcd",
"title": "Haircut",
"type": "classic-checkout"
},
"start_date": "2026-05-27T14:00:00.000Z",
"end_date": "2026-05-27T14:30:00.000Z",
"timezone": "Europe/Paris",
"customer": {
"name": "Jane Smith",
"email": "[email protected]",
"phone": "+33612345678",
"locale": "en"
},
"form_data": {
"Special requests": "Window seat"
},
"quantity": 1,
"unit_quantity": 1,
"quantity_details": [],
"price": { "amount": 35, "currency": "EUR" },
"confirmation_status": "confirmed",
"attendance": "booked",
"financial_status": "paid",
"is_canceled": false,
"teammates": [
{
"id": "aaa111bbb222ccc333ddd444",
"firstname": "Marie",
"lastname": "Dupont"
}
],
"order_id": "gid://shopify/Order/123456789",
"subscription_id": null,
"created_at": "2026-05-20T09:15:00.000Z",
"updated_at": "2026-05-20T09:15:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
}
Understanding key booking fields
quantity and unit_quantity work together. For a classic service like a haircut, quantity is the number of customers attending and unit_quantity is 1. For a multiday service like a hotel stay, quantity is the number of rooms and unit_quantity is the number of nights. For a multislot service, quantity is always 1 and unit_quantity is the number of booked slots. Total billable units are always quantity x unit_quantity.
quantity_details is the detailed breakdown. It can contain three types of entries: default for an anonymous count, participant for a typed participant like "Adult" or "Child", or equipment for a bookable resource like "Court 3". Legacy bookings may have an empty array here.
confirmation_status is confirmed, pending, or declined. Some services require manual confirmation by the admin before a booking becomes active.
attendance tracks the customer's attendance lifecycle: booked, pending, arrived, started, completed, no-show, delayed, paid.
order_id is the Shopify order ID when the booking went through Shopify checkout. It is null for manual bookings and API-created bookings.
subscription_id links the booking to a subscription plan, for example a 10-class pass.
Create a booking via API
POST /public-api/v1/bookings
Creates a booking programmatically. This is treated as a manual booking, meaning no Shopify order is created. However, all the usual automations still trigger normally: confirmation emails to the customer, notification emails and SMS to the team, and calendar sync with Google Calendar or Outlook.
This endpoint is what you use when building a custom booking frontend, a mobile app, a kiosk, or when importing bookings from another system.
Required fields:
service_id (string): the ID of the service, which you get from List Services.
start_date (ISO 8601 datetime): when the booking starts.
end_date (ISO 8601 datetime): when the booking ends.
timezone (string): IANA timezone, for example Europe/Paris, America/New_York, Asia/Tokyo.
customer (object): must contain at least email or phone. It can also include name, firstname, lastname, and locale, which is the language code for the customer's notification emails.
Optional fields:
quantity (integer, default 1): top-level units booked. Ignored if quantity_details is provided.
unit_quantity (integer, default 1): sub-units per top-level unit.
quantity_details (array): detailed breakdown by participant type or equipment. When provided, quantity is auto-calculated as the sum.
teammate_id (string): force-assign a specific team member.
subscription_id (string): link this booking to an existing subscription and deduct one credit.
form_data (object): custom key or value pairs from your booking form.
price (number, default 0): net total price for the booking.
currency (string): ISO 4217 code like USD, EUR, or GBP. Defaults to your shop's currency.
Example: create a simple booking
A customer books a 30-minute haircut with a specific stylist:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-01T10:00:00.000Z",
"end_date": "2026-06-01T10:30:00.000Z",
"timezone": "Europe/Paris",
"customer": {
"email": "[email protected]",
"name": "John Doe",
"phone": "+33612345678",
"locale": "en"
},
"teammate_id": "aaa111bbb222ccc333ddd444",
"quantity": 1,
"price": 35,
"currency": "EUR"
}'
Cowlendar creates the booking and triggers the confirmation email, SMS, and calendar invite for both the customer and the team member.
Example: group booking with typed participants
A family books a guided tour: 3 adults and 2 children at different price points:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-15T09:00:00.000Z",
"end_date": "2026-06-15T11:00:00.000Z",
"timezone": "America/New_York",
"customer": {
"email": "[email protected]",
"firstname": "Sarah",
"lastname": "Miller",
"phone": "+15551234567",
"locale": "en"
},
"quantity_details": [
{
"type": "participant",
"name": "Adult",
"quantity": 3,
"participant_id": "adult_id_from_service"
},
{
"type": "participant",
"name": "Child (under 12)",
"quantity": 2,
"participant_id": "child_id_from_service"
}
],
"price": 175,
"currency": "USD"
}'
The participant_id values come from the service's participants[] array in the List Services response. The total quantity is automatically set to 5 (3 + 2).
Example: booking with equipment
A customer books a tennis court for 1 hour:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-10T16:00:00.000Z",
"end_date": "2026-06-10T17:00:00.000Z",
"timezone": "Europe/London",
"customer": {
"email": "[email protected]",
"name": "Tom Wilson"
},
"quantity_details": [
{
"type": "equipment",
"name": "Court 3",
"quantity": 1,
"equipment_id": "eee555fff666ggg777hhh888",
"equipment_sku": "COURT3",
"capacity": 4
}
],
"price": 25,
"currency": "GBP"
}'
The equipment_id and equipment_sku come from the service's equipments[] array. For equipment with tracking_type: "same", use any SKU, typically base. For tracking_type: "unique", use the specific variants[].sku for the unit you want.
Example: subscription-linked booking
A yoga studio member uses one credit from their monthly 10-class pass:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-05T08:00:00.000Z",
"end_date": "2026-06-05T09:00:00.000Z",
"timezone": "America/Los_Angeles",
"customer": {
"email": "[email protected]",
"name": "Lisa Chen",
"locale": "en"
},
"subscription_id": "fff000eee111ddd222ccc333",
"price": 0
}'
The subscription must be active, belong to the same service, and have remaining_credits > 0. Cowlendar automatically decrements the credit count and sends the subscription update email to the customer.
Error codes
400 Validation error. A required field is missing or in the wrong format. The error body tells you which field.
401 Missing or invalid token.
403 Shop is inactive or access is restricted.
404 Service not found.
422 Booking rejected. The time slot is already taken, the team member is unavailable, the equipment is at capacity, or the subscription has no remaining credits. Check the error message for the specific reason.
429 Rate limit exceeded. Space out your requests and retry after a moment.
Pagination
All list endpoints use cursor-based pagination. When the response includes "has_more": true, pass the next_cursor value as the cursor parameter in your next request:
curl "https://app.cowlendar.com/public-api/v1/bookings?cursor=eyJpZCI6IjY3ODlhYmNkIn0=&limit=50" -H "Authorization: Bearer YOUR_TOKEN_HERE"
The sort order is preserved across pages. Maximum page size is 100 results.
Webhooks: real-time booking notifications
While the API lets you pull data on demand, webhooks push data to you instantly. Every time a booking is created, confirmed, cancelled, or rescheduled, Cowlendar sends a signed HTTP POST to the URL you configure.
Webhooks are essential for building real-time integrations: CRM syncs, Slack notifications, automated email sequences, live dashboards, or any workflow that needs to react to booking events as they happen.
Set up a webhook endpoint
Step 1: In your Shopify admin, open the Cowlendar app.
Step 2: Go to Settings > Webhooks.
Step 3: Click Add endpoint.
Step 4: Enter a name for your endpoint, for example "HubSpot CRM", "Zapier", or "Analytics pipeline".
Step 5: Paste your HTTPS URL. The URL must use HTTPS. If you are using Zapier or Make, paste the webhook URL they provide.
Step 6: Check the events you want to receive. You can pick any combination.
Step 7: Click Create.
Cowlendar displays a signing secret starting with whsec_. Copy it immediately and store it securely. This secret is shown only once and you need it to verify incoming webhook requests on your server.
You can edit any endpoint later to change the URL or enable or disable specific events:
The 8 webhook events
booking.created fires when a new booking is created from any source: the storefront widget, the admin panel, POS, or the Public API.
booking.confirmed fires when a pending booking is manually confirmed by the admin.
booking.declined fires when a pending booking is declined.
booking.canceled fires when a booking is canceled by the customer or the admin.
booking.rescheduled fires when a booking's date or assigned team members are changed.
booking.attendance_changed fires when the attendance status changes, for example from "booked" to "completed" or when it is marked as "no-show".
subscription.created fires when a new recurring subscription is created.
subscription.billing_failed fires when a subscription billing attempt fails. After 3 consecutive failures, the subscription contract is automatically canceled on Shopify and the local status changes to canceled.
What a webhook payload looks like
Every webhook delivery is an HTTP POST with a JSON body. Here is a complete booking.created example:
{
"id": "evt_2b86f80a-1c3d-4e5f-9a8b-7c6d5e4f3a2b",
"type": "booking.created",
"created_at": "2026-05-20T09:15:00.000Z",
"shop_domain": "yourshop.myshopify.com",
"data": {
"id": "abc123def456ghi789jkl012",
"booking_str": "COW-1234",
"service": {
"id": "6789abcdef0123456789abcd",
"title": "Haircut",
"type": "classic-checkout"
},
"start_date": "2026-05-27T14:00:00.000Z",
"end_date": "2026-05-27T14:30:00.000Z",
"timezone": "Europe/Paris",
"customer": {
"name": "Jane Smith",
"email": "[email protected]",
"phone": "+33612345678",
"locale": "en"
},
"form_data": {},
"quantity": 1,
"unit_quantity": 1,
"quantity_details": [],
"price": { "amount": 35, "currency": "EUR" },
"confirmation_status": "confirmed",
"attendance": "booked",
"financial_status": null,
"is_canceled": false,
"teammates": [
{
"id": "aaa111bbb222ccc333ddd444",
"firstname": "Marie",
"lastname": "Dupont"
}
],
"order_id": null,
"subscription_id": null,
"created_at": "2026-05-20T09:15:00.000Z",
"updated_at": "2026-05-20T09:15:00.000Z"
}
}
The data field contains the full booking or subscription object in exactly the same structure as the API response.
For subscription.billing_failed events, the payload includes an additional previous object with the failure_count before the current attempt, so you can track escalation, for example by sending an urgent email after the second failure.
Request headers on every webhook delivery
X-Cowlendar-Event-Id is the unique ID of the event. Cowlendar reuses the same ID when retrying a failed delivery. Use it to deduplicate on your side.
X-Cowlendar-Event-Type is the event type string, for example booking.created.
X-Cowlendar-Timestamp is the Unix timestamp in seconds when Cowlendar signed the request.
X-Cowlendar-Signature is the HMAC-SHA256 signature in the format t=<timestamp>,v1=<hex>.
Verify the webhook signature
Every webhook request is signed so you can confirm it really came from Cowlendar and was not tampered with. The signature is computed as HMAC-SHA256(secret, timestamp + "." + raw_body) using your endpoint's signing secret.
You should also reject requests with a timestamp older than 5 minutes to prevent replay attacks.
Node.js (Express):
import crypto from "crypto";
import express from "express";
const app = express();
app.post(
"/webhooks/cowlendar",
express.raw({ type: "application/json" }),
(req, res) => {
const sigHeader =
req.header("X-Cowlendar-Signature") || "";
const timestamp =
req.header("X-Cowlendar-Timestamp") || "";
const rawBody = req.body.toString("utf8");
// Reject requests older than 5 minutes
if (
Math.abs(Date.now() / 1000 - parseInt(timestamp, 10))
> 300
) {
return res.status(400).send("Timestamp too old");
}
const expected = crypto
.createHmac(
"sha256",
process.env.COWLENDAR_WEBHOOK_SECRET
)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const received = sigHeader
.split(",")
.find((p) => p.startsWith("v1="))
?.slice(3);
if (
!received ||
!crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
)
) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody);
// Short-circuit test pings
if (event.type === "webhook.ping") {
return res.status(200).send("pong");
}
// Handle real events here
res.status(200).send("ok");
}
);
Python (Flask):
import hmac, hashlib, os, time, json
from flask import Flask, request
app = Flask(__name__)
@app.post("/webhooks/cowlendar")
def cowlendar_webhook():
sig_header = request.headers.get(
"X-Cowlendar-Signature", ""
)
timestamp = request.headers.get(
"X-Cowlendar-Timestamp", ""
)
raw_body = request.get_data(as_text=True)
# Reject requests older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return "Timestamp too old", 400
expected = hmac.new(
os.environ["COWLENDAR_WEBHOOK_SECRET"].encode(),
f"{timestamp}.{raw_body}".encode(),
hashlib.sha256,
).hexdigest()
received = next(
(p[3:] for p in sig_header.split(",")
if p.startswith("v1=")),
None,
)
if not received or not hmac.compare_digest(
expected, received
):
return "Invalid signature", 401
event = json.loads(raw_body)
if event["type"] == "webhook.ping":
return "pong", 200
# Handle real events here
return "ok", 200
PHP:
<?php
$secret = getenv("COWLENDAR_WEBHOOK_SECRET");
$rawBody = file_get_contents("php://input");
$sigHeader = $_SERVER["HTTP_X_COWLENDAR_SIGNATURE"] ?? "";
$timestamp = $_SERVER["HTTP_X_COWLENDAR_TIMESTAMP"] ?? "";
// Reject requests older than 5 minutes
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(400);
exit("Timestamp too old");
}
$expected = hash_hmac(
"sha256",
$timestamp . "." . $rawBody,
$secret
);
$received = null;
foreach (explode(",", $sigHeader) as $part) {
if (str_starts_with($part, "v1=")) {
$received = substr($part, 3);
break;
}
}
if (!$received || !hash_equals($expected, $received)) {
http_response_code(401);
exit("Invalid signature");
}
$event = json_decode($rawBody, true);
if ($event["type"] === "webhook.ping") {
http_response_code(200);
exit("pong");
}
// Handle real events here
http_response_code(200);
echo "ok";
Test your webhook before going live
Each webhook endpoint has a Test button in the admin. Clicking it sends a synthetic webhook.ping event to your URL. This event uses the same signing, headers, and retry behavior as real events, but with a harmless test payload:
{
"id": "evt_2b86f80a...",
"type": "webhook.ping",
"created_at": "2026-05-13T14:57:57.704Z",
"shop_domain": "yourshop.myshopify.com",
"data": {
"message": "This is a test event from Cowlendar. If you can read this, your endpoint is reachable."
}
}
Use this to confirm that Cowlendar can reach your URL, that DNS, HTTPS, and firewall settings are working, and to validate your HMAC signature verification code without creating a real booking. A real booking would send emails to a customer, create calendar events, and potentially trigger a Shopify order.
After clicking Test, check the See deliveries button to see the HTTP status code your endpoint returned. A green 200 means everything is working.
Retry behavior and auto-disable
If your endpoint returns a 5xx error, a 429, or a network timeout, Cowlendar retries on this schedule:
After 1 minute, after 5 minutes, after 25 minutes, after 2 hours, after 10 hours, after 50 hours. That is 6 total attempts spanning roughly 3 days.
After approximately 20 consecutive failures across all events, the endpoint is automatically disabled and Cowlendar emails the shop owner. You can re-enable the endpoint from Settings > Webhooks once your server is back online.
The same X-Cowlendar-Event-Id is reused on retries. Always store processed event IDs and skip duplicates.
Any 2xx response counts as success. You do not need to return a specific body or content type.
Webhook best practices
Respond within 10 seconds. If your handler needs to do heavy work, such as calling another API, writing to a database, or running a complex operation, do it asynchronously. Accept the webhook immediately with a 200, then process it in a background job or queue.
Deduplicate using the event ID. Store every X-Cowlendar-Event-Id you process. If you receive the same ID again, skip it. Cowlendar retries with the same ID on failure.
Use constant-time comparison for signatures. crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python, and hash_equals in PHP. Regular string comparison like === is vulnerable to timing attacks.
Do not retry from your side. Cowlendar handles retries automatically. If your server had a temporary issue, return 5xx and Cowlendar will come back on its retry schedule.
Handle webhook.ping explicitly. Return 200 immediately for ping events. This lets you use the Test button without triggering your business logic.
Current limitations
Bookings created via API are manual bookings. They do not create a Shopify order. If your service normally uses Shopify checkout, the API bypasses it. Confirmation emails, SMS, and calendar sync still work normally.
No availability endpoint yet. The current API lets you read services and bookings, and create bookings. It does not expose available time slots. To check whether a time slot is available, attempt to create the booking and handle the 422 response if the slot is taken. An availability endpoint may be added in a future update.
No update or delete endpoints yet. You can create and read bookings, but you cannot update, reschedule, or cancel them via API. Manage those actions through the Cowlendar admin for now.
Rate limits apply. If you send too many requests in a short window, the API returns 429. For bulk operations like exporting all bookings, add a short delay between paginated requests, for example 200 to 500 ms.
Webhook secrets are shown only once. If you lose your signing secret, delete the endpoint and create a new one to get a fresh secret.
Webhooks require HTTPS. HTTP endpoints are not accepted.
FAQ
Can I use the Cowlendar API with Zapier or Make without writing code?
Yes. For webhooks, receiving events from Cowlendar, create a "Custom Webhook" trigger in Zapier or Make, copy the URL they give you, and add it as an endpoint in Cowlendar Settings > Webhooks. Every booking event triggers your automation. For the API, sending requests to Cowlendar, use the "HTTP Request" action in Zapier or Make to call any Cowlendar API endpoint with your Bearer token.
Do bookings created via the API send emails to customers?
Yes. API-created bookings are treated exactly like manual bookings. Confirmation emails, SMS notifications, and calendar sync all trigger automatically, just as they would for a booking made through the admin panel.
What happens if my webhook endpoint goes down?
Cowlendar retries failed deliveries 6 times over approximately 3 days. After about 20 consecutive failures, the endpoint is auto-disabled and you receive an email. Re-enable it from Settings > Webhooks once your server is back.
Can I have multiple webhook endpoints?
Yes. Create as many as you need, each with different event selections and URLs. For example, one endpoint for your CRM listening to booking.created and booking.canceled, and another for your analytics pipeline listening to all events.
Can I revoke an API token?
Yes. Go to Settings > Public API and click the Revoke button. The token stops working immediately.
Is there a sandbox or staging environment?
Not currently. Use the webhook.ping Test button to validate your webhook setup without creating real bookings. For API testing, we recommend creating a test service in your Cowlendar admin that you use only for development.
Where is the full API reference?
The interactive API documentation with all schemas, parameters, and response examples is at https://app.cowlendar.com/public-api/docs