Se gestisci un'attività che fa affidamento sulle prenotazioni, probabilmente avrai bisogno dei dati di prenotazione in più di un posto. Il tuo CRM deve sapere quando un nuovo cliente prenota. Il tuo ERP ha bisogno dei dati sulle entrate. Il tuo strumento di marketing deve attivare un'e-mail di benvenuto. La tua app personalizzata deve creare prenotazioni in modo programmatico.
Le API pubbliche e i Webhook di Cowlendar rendono tutto questo possibile. L'API ti consente di leggere i tuoi servizi e prenotazioni e di creare nuove prenotazioni da qualsiasi sistema esterno. I webhook inviano notifiche in tempo reale al tuo server ogni volta che succede qualcosa: una prenotazione viene creata, confermata, annullata, riprogrammata o si verifica un evento di abbonamento.
Entrambe le funzionalità sono attualmente in Beta.
Cosa puoi creare con l'API Cowlendar?
Ecco gli scenari di integrazione reali che i commercianti Cowlendar stanno costruendo in questo momento:
Sincronizza ogni prenotazione con HubSpot, Salesforce o qualsiasi CRM. Quando un cliente prenota un taglio di capelli, una lezione di yoga o un noleggio, un webhook si attiva istantaneamente con i dati completi della prenotazione (nome, email, telefono, servizio, data, prezzo, membro del team). Il tuo CRM crea o aggiorna il contatto automaticamente, senza inserimento manuale dei dati. Crea la tua pagina di prenotazione o app mobile. Utilizza l'API per recuperare i tuoi servizi, incluse durate, membri del team, attrezzature e categorie di partecipanti, e crea prenotazioni dal tuo frontend. La prenotazione attiva le stesse e-mail di conferma, notifiche SMS e sincronizzazione Google Calendar di qualsiasi prenotazione effettuata tramite il widget Cowlendar. Automatizza i flussi di lavoro con Zapier o Make senza codice. Punta un webhook Cowlendar al tuo URL webhook Zapier o Make. Ogni evento di prenotazione diventa un trigger. Invia una notifica Slack quando qualcuno prenota, aggiunge una riga a Fogli Google, crea una scheda Trello o attiva una sequenza e-mail in Klaviyo o Mailchimp. Inserisci i dati di prenotazione nel tuo strumento di reporting o BI. Utilizza l'endpoint Elenca prenotazioni per estrarre tutte le prenotazioni entro un intervallo di date, filtrate per stato, servizio, partecipazione o e-mail del cliente. Esporta nel tuo data warehouse, Fogli Google o Airtable per dashboard personalizzate. Collega le prenotazioni ai crediti di abbonamento. Quando crei una prenotazione tramite API, passa un subscription_id per detrarre automaticamente un credito dal piano del cliente, ad esempio un pass yoga per 10 lezioni. Collega le prenotazioni POS o di amministrazione a sistemi esterni. I webhook si attivano per tutte le fonti di prenotazione: il widget della vetrina, il pannello di amministrazione, il POS e l'API stessa. Niente passa attraverso le fessure.
Autenticazione
Ogni richiesta API richiede un token Bearer generato dal tuo amministratore Cowlendar. I token hanno accesso completo in lettura e scrittura alle prenotazioni e ai servizi del tuo negozio. Trattateli come password: non affidateli al controllo della versione e non condivideteli nei canali pubblici.
Come generare il tuo token API
Passo 1: Nell'amministratore Shopify, apri l'app Cowlendar.
Passo 2: Vai a Settings > Public API.
Passo 3: Fare clic su Generate token.
Passo 4: Dai al tuo token un nome descrittivo in modo da poterlo identificare in seguito, ad esempio "HubSpot sync", "Zapier" o "Mobile app".
Passo 5: Fare clic su Generate . Cowlendar ti mostra il token completo solo una volta. Copialo immediatamente e archivialo in un luogo sicuro come un gestore di password o nelle variabili di ambiente del tuo server.
Puoi revocare qualsiasi token in qualsiasi momento dalla stessa pagina. La revoca di un token interrompe immediatamente tutte le richieste API che lo utilizzano.
Usando il tuo gettone
Includi il token nell'intestazione Authorization di ogni richiesta API:
Authorization: Bearer YOUR_TOKEN_HERE
Esempio con arricciatura:
curl https://app.cowlendar.com/public-api/v1/services -H "Authorization: Bearer YOUR_TOKEN_HERE"
Se il token manca o non è valido, l'API restituisce 401.
Endpoint API
URL di base: https://app.cowlendar.com
Elenco servizi
GET /public-api/v1/services
Restituisce tutti i servizi non archiviati nel tuo negozio. Utilizza questo endpoint per scoprire ID servizio, tipi, durate, membri del team, attrezzature e categorie di partecipanti prima di creare prenotazioni tramite l'API.
Parametri della query:
limit (opzionale, 1-100): numero di risultati per pagina.
cursor (opzionale): cursore di impaginazione da una risposta precedente.
is_active (facoltativo, vero o falso): filtra per servizi attivi o inattivi.
type (opzionale): filtra per tipo di servizio. Valori: classic-checkout, classic-no-checkout, multiday-checkout, multiday-no-checkout, multislot-checkout, multislot-no-checkout, fullday-checkout, fullday-no-checkout.
q (opzionale, max 120 caratteri): ricerca servizi per titolo.
sort (opzionale): -id (predefinito, prima il più recente), id (prima il più vecchio), title (dalla A alla Z), -title (dalla Z alla A).
Esempio di richiesta:
curl "https://app.cowlendar.com/public-api/v1/services?is_active=true&limit=10" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Esempio di risposta (abbreviato):
{
"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
}
}
Comprendere i principali settori dei servizi
avs_type spiega come questo servizio gestisce la disponibilità e determina se è necessario passare un teammate_id durante la creazione delle prenotazioni:
avs_type: "teammates" significa che ogni membro del team ha il proprio programma indipendente. Quando si crea una prenotazione, passare uno teammate_id dall'array teammates. Se lo ometti, il proprietario del negozio viene utilizzato come fallback.
avs_type: "service" significa che il servizio stesso possiede la sua disponibilità. Il membro del team assegnato viene determinato automaticamente dal momento della prenotazione. Puoi comunque passare uno teammate_id per forzare l'assegnazione a una persona specifica.
avs_type: "equipment" significa che la disponibilità è per attrezzatura. Simile alla modalità di servizio, ma legata alla capacità dell'apparecchiatura.
enable_participants indica se il servizio utilizza categorie di partecipanti digitate come "Adulto", "Bambino" o "Anziano". Quando true, l'array participants contiene le categorie con i relativi ID, quantità minima e massima e valori predefiniti. Questi ID sono necessari quando si creano prenotazioni con una ripartizione dettagliata dei partecipanti. attrezzature contiene risorse prenotabili come stanze, campi, veicoli o biciclette con le relative varianti e SKU. Per le apparecchiature con tracking_type: "same", tutte le unità sono intercambiabili ed è possibile utilizzare qualsiasi SKU, in genere base. Per tracking_type: "unique", ogni unità ha il proprio SKU e devi scegliere da variants[].sku. tipo indica il modello di prenotazione. Il suffisso -checkout o -no-checkout indica se il servizio utilizza il checkout Shopify. Il prefisso indica il modello di programmazione: classic per una singola fascia oraria, multiday per più giorni come soggiorni in hotel, multislot per più fasce orarie consecutive e fullday per una prenotazione di un'intera giornata.
Elenco prenotazioni
GET /public-api/v1/bookings
Restituisce le prenotazioni per il tuo negozio. Questo è l'endpoint che utilizzi per le sincronizzazioni CRM, le esportazioni di report e i dashboard di prenotazione personalizzati.
Parametri della query:
limit (opzionale, 1-100): risultati per pagina.
cursor (opzionale): cursore di impaginazione.
start (opzionale, ISO 8601 datetime): solo prenotazioni che iniziano a partire da questa data.
end (opzionale, ISO 8601 datetime): solo prenotazioni che iniziano prima di questa data.
service_id (opzionale, array di ID): filtra per uno o più servizi.
subscription_id (opzionale, array di ID): filtra per abbonamenti.
status (opzionale, array): confirmed, pending, declined, canceled.
attendance (opzionale, array): booked, pending, arrived, started, completed, no-show, delayed, paid.
customer_email (opzionale): filtra per email esatta del cliente.
sort (opzionale): -id (predefinito, prima il più recente), id (prima il più vecchio), start_date (prima il meno recente), -start_date (prima il più recente).
Esempio: ricevi tutte le prenotazioni confermate per la prossima settimana:
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"
Esempio: trova tutte le prenotazioni per un cliente specifico:
curl "https://app.cowlendar.com/public-api/v1/[email protected]" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Esempio: esportare tutte le mancate presentazioni da un servizio specifico:
curl "https://app.cowlendar.com/public-api/v1/bookings?attendance=no-show&service_id=6789abcdef0123456789abcd" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Esempio di risposta (abbreviato):
{
"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
}
}
Comprendere i campi chiave della prenotazione
quantity e unit_quantity lavorano insieme. Per un servizio classico come il taglio dei capelli, quantity è il numero di clienti presenti e unit_quantity è 1. Per un servizio di più giorni come un soggiorno in hotel, quantity è il numero di camere e unit_quantity è il numero di notti. Per un servizio multislot, quantity è sempre 1 e unit_quantity è il numero di slot prenotati. Le unità totali fatturabili sono sempre quantity x unit_quantity. quantity_details è la ripartizione dettagliata. Può contenere tre tipi di voci: default per un conteggio anonimo, participant per un partecipante digitato come "Adulto" o "Bambino" o equipment per una risorsa prenotabile come "Corte 3". Le prenotazioni precedenti potrebbero avere un array vuoto qui. confirmation_status è confirmed, pending o declined. Alcuni servizi richiedono la conferma manuale da parte dell'amministratore prima che la prenotazione diventi attiva. presenza tiene traccia del ciclo di vita della presenza del cliente: booked, pending, arrived, started, completed, no-show, delayed, paid. order_id è l'ID dell'ordine Shopify quando la prenotazione è passata attraverso il checkout Shopify. È null per le prenotazioni manuali e le prenotazioni create tramite API. subscription_id collega la prenotazione a un piano di abbonamento, ad esempio un abbonamento da 10 classi.
Crea una prenotazione tramite API
POST /public-api/v1/bookings
Crea una prenotazione a livello di codice. Questa viene trattata come una prenotazione manuale, il che significa che non viene creato alcun ordine Shopify. Tuttavia, tutte le consuete automazioni si attivano ancora normalmente: e-mail di conferma al cliente, e-mail di notifica e SMS al team e sincronizzazione del calendario con Google Calendar o Outlook.
Questo endpoint è quello che utilizzi quando crei un frontend di prenotazione personalizzato, uno app mobile, un chiosco o quando importi prenotazioni da un altro sistema.
Campi obbligatori:
service_id (stringa): l'ID del servizio, che ottieni da List Services.
start_date (ISO 8601 datetime): quando inizia la prenotazione.
end_date (ISO 8601 datetime): quando termina la prenotazione.
timezone (stringa): fuso orario IANA, ad esempio Europe/Paris, America/New_York, Asia/Tokyo.
customer (oggetto): deve contenere almeno email o phone. Può includere anche name, firstname, lastname e locale, che è il codice lingua per le e-mail di notifica del cliente.
Campi facoltativi:
quantity (numero intero, default 1): unità di livello superiore prenotate. Ignorato se viene fornito quantity_details.
unit_quantity (numero intero, default 1): sottounità per unità di livello superiore.
quantity_details (array): suddivisione dettagliata per tipologia di partecipante o attrezzatura. Se fornito, quantity viene calcolato automaticamente come somma.
teammate_id (stringa): assegna forzatamente un membro specifico del team.
subscription_id (stringa): collega questa prenotazione a un abbonamento esistente e detrae un credito.
form_data (oggetto): chiavi personalizzate o coppie di valori dal modulo di prenotazione.
price (numero, predefinito 0): prezzo totale netto della prenotazione.
currency (stringa): codice ISO 4217 come USD, EUR o GBP. Il valore predefinito è la valuta del tuo negozio.
Esempio: creare una prenotazione semplice
Un cliente prenota un taglio di capelli di 30 minuti con uno stilista specifico:
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 crea la prenotazione e attiva l'e-mail di conferma, l'SMS e l'invito al calendario sia per il cliente che per il membro del team.
Esempio: prenotazione di gruppo con partecipanti digitati
Una famiglia prenota una visita guidata: 3 adulti e 2 bambini a prezzi differenziati:
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"
}'
I valori participant_id provengono dall'array participants[] del servizio nella risposta List Services. Il totale quantity viene impostato automaticamente su 5 (3 + 2).
Esempio: prenotazione con attrezzatura
Un cliente prenota un campo da tennis per 1 ora:
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"
}'
equipment_id e equipment_sku provengono dall'array equipments[] del servizio. Per le apparecchiature con tracking_type: "same", utilizzare qualsiasi SKU, in genere base. Per tracking_type: "unique", utilizzare lo variants[].sku specifico per l'unità desiderata.
Esempio: prenotazione collegata ad un abbonamento
Un membro di uno studio di yoga utilizza un credito dal suo abbonamento mensile di 10 lezioni:
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
}'
L'abbonamento deve essere active, appartenere allo stesso servizio e avere remaining_credits > 0. Cowlendar diminuisce automaticamente il conteggio del credito e invia al cliente l'e-mail di aggiornamento dell'abbonamento.
Codici di errore
400 Errore di convalida. Un campo obbligatorio manca o è nel formato sbagliato. Il corpo dell'errore indica quale campo.
401 Token mancante o non valido.
403 Il negozio è inattivo o l'accesso è limitato.
404 Servizio non trovato.
422 Prenotazione rifiutata. La fascia oraria è già occupata, il membro del team non è disponibile, l'attrezzatura è al completo o l'abbonamento non ha crediti rimanenti. Controllare il messaggio di errore per il motivo specifico.
429 Limite di velocità superato. Distanzia le tue richieste e riprova dopo un momento.
Impaginazione
Tutti gli endpoint dell'elenco utilizzano l'impaginazione basata sul cursore. Quando la risposta include "has_more": true, passa il valore next_cursor come parametro cursor nella richiesta successiva:
curl "https://app.cowlendar.com/public-api/v1/bookings?cursor=eyJpZCI6IjY3ODlhYmNkIn0=&limit=50" -H "Authorization: Bearer YOUR_TOKEN_HERE"
L'ordinamento viene mantenuto tra le pagine. La dimensione massima della pagina è di 100 risultati.
Webhook: notifiche di prenotazione in tempo reale
Mentre l'API ti consente di estrarre i dati su richiesta, i webhook ti inviano i dati istantaneamente. Ogni volta che una prenotazione viene creata, confermata, annullata o riprogrammata, Cowlendar invia un POST HTTP firmato all'URL configurato.
I webhook sono essenziali per creare integrazioni in tempo reale: sincronizzazioni CRM, notifiche Slack, sequenze e-mail automatizzate, dashboard live o qualsiasi flusso di lavoro che deve reagire agli eventi di prenotazione non appena si verificano.
Configura un endpoint webhook
Passo 1: Nell'amministratore Shopify, apri l'app Cowlendar.
Passo 2: Vai a Settings > Webhooks.
Passo 3: Fare clic su Add endpoint.
Passo 4: Inserisci un nome per il tuo endpoint, ad esempio "HubSpot CRM", "Zapier" o "Analytics pipeline".
Passo 5: Incolla il tuo URL HTTPS. L'URL deve utilizzare HTTPS. Se utilizzi Zapier o Make, incolla l'URL del webhook fornito.
Passo 6: Controlla gli eventi che desideri ricevere. Puoi scegliere qualsiasi combinazione.
Passo 7: Fare clic su Create.
Cowlendar visualizza un segreto di firma che inizia con whsec_. Copialo immediatamente e conservalo in modo sicuro. Questo segreto viene mostrato solo una volta e ti serve per verificare le richieste webhook in entrata sul tuo server.
Puoi modificare qualsiasi endpoint in un secondo momento per cambiare l'URL o abilitare o disabilitare eventi specifici:
Gli 8 eventi webhook
booking.created si attiva quando viene creata una nuova prenotazione da qualsiasi fonte: il widget della vetrina, il pannello di amministrazione, il POS o l'API pubblica. booking.confirmed si attiva quando una prenotazione in sospeso viene confermata manualmente dall'amministratore. booking.declined si attiva quando una prenotazione in sospeso viene rifiutata. booking.canceled si attiva quando una prenotazione viene annullata dal cliente o dall'amministratore. booking.rescheduled si attiva quando la data di prenotazione o i membri del team assegnati vengono modificati. booking.attendance_changed si attiva quando lo stato di partecipazione cambia, ad esempio da "prenotato" a "completato" o quando viene contrassegnato come "mancata presentazione". subscription.created si attiva quando viene creato un nuovo abbonamento ricorrente. subscription.billing_failed si attiva quando un tentativo di fatturazione di un abbonamento fallisce. Dopo 3 errori consecutivi, il contratto di abbonamento viene automaticamente annullato su Shopify e lo stato locale cambia in canceled.
Che aspetto ha un payload webhook
Ogni consegna di webhook è un POST HTTP con un corpo JSON. Ecco un esempio booking.created completo:
{
"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"
}
}
Il campo data contiene l'intero oggetto di prenotazione o abbonamento esattamente nella stessa struttura della risposta API.
Per gli eventi subscription.billing_failed, il payload include un oggetto previous aggiuntivo con failure_count prima del tentativo corrente, in modo da poter tenere traccia dell'escalation, ad esempio inviando un'e-mail urgente dopo il secondo errore.
Richiedi intestazioni su ogni consegna di webhook
X-Cowlendar-Event-Id è l'ID univoco dell'evento. Cowlendar riutilizza lo stesso ID quando ritenta una consegna non riuscita. Usalo per deduplicare dalla tua parte.
X-Cowlendar-Event-Type è la stringa del tipo di evento, ad esempio booking.created.
X-Cowlendar-Timestamp è il timestamp Unix in secondi in cui Cowlendar ha firmato la richiesta.
X-Cowlendar-Signature è la firma HMAC-SHA256 nel formato t=<timestamp>,v1=<hex>.
Verifica la firma del webhook
Ogni richiesta webhook è firmata in modo da poter confermare che provenga realmente da Cowlendar e che non sia stata manomessa. La firma viene calcolata come HMAC-SHA256(secret, timestamp + "." + raw_body) utilizzando il segreto di firma dell'endpoint.
Dovresti anche rifiutare le richieste con un timestamp più vecchio di 5 minuti per prevenire attacchi di replay.
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");
}
);
Pitone (fiaschetta):
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";
Metti alla prova il tuo webhook prima di andare in diretta
Ogni endpoint webhook ha un pulsante Test nell'amministratore. Facendo clic su di esso viene inviato un evento sintetico webhook.ping al tuo URL. Questo evento utilizza la stessa firma, intestazioni e comportamento di ripetizione degli eventi reali, ma con un payload di test innocuo:
{
"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."
}
}
Utilizzalo per confermare che Cowlendar può raggiungere il tuo URL, che le impostazioni DNS, HTTPS e firewall funzionino e per convalidare il codice di verifica della firma HMAC senza creare una vera prenotazione. Una prenotazione reale invierebbe e-mail a un cliente, creerebbe eventi di calendario e potenzialmente attiverebbe un ordine Shopify.
Dopo aver fatto clic su Test, controlla il pulsante See deliveries per vedere il codice di stato HTTP restituito dall'endpoint. Uno 200 verde significa che tutto funziona.
Riprova il comportamento e disattiva automaticamente
Se l'endpoint restituisce un errore 5xx, 429 o un timeout di rete, Cowlendar riprova in base a questa pianificazione:
Dopo 1 minuto, dopo 5 minuti, dopo 25 minuti, dopo 2 ore, dopo 10 ore, dopo 50 ore. Si tratta di 6 tentativi totali nell'arco di circa 3 giorni.
Dopo circa 20 errori consecutivi in tutti gli eventi, l'endpoint viene automaticamente disabilitato e Cowlendar invia un'e-mail al proprietario del negozio. Puoi riattivare l'endpoint da Settings > Webhooks una volta che il server è di nuovo online.
Lo stesso X-Cowlendar-Event-Id viene riutilizzato nei tentativi. Memorizza sempre gli ID evento elaborati e ignora i duplicati.
Qualsiasi risposta 2xx conta come successo. Non è necessario restituire un corpo o un tipo di contenuto specifico.
Best practice per i webhook
Rispondi entro 10 secondi. Se il tuo gestore deve svolgere un lavoro pesante, come chiamare un'altra API, scrivere su un database o eseguire un'operazione complessa, fallo in modo asincrono. Accetta immediatamente il webhook con 200, quindi elaboralo in un processo o in una coda in background. Deduplica utilizzando l'ID evento. Archivia ogni X-Cowlendar-Event-Id elaborato. Se ricevi di nuovo lo stesso ID, saltalo. Cowlendar riprova con lo stesso ID in caso di errore. Utilizza il confronto in tempo costante per le firme. crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python e hash_equals in PHP. Il confronto regolare di stringhe come === è vulnerabile agli attacchi temporali. Non riprovare dal tuo lato. Cowlendar gestisce i tentativi automaticamente. Se il tuo server ha riscontrato un problema temporaneo, restituisci 5xx e Cowlendar tornerà nella pianificazione dei tentativi. Gestisci webhook.ping in modo esplicito. Restituisci 200 immediatamente per gli eventi ping. Ciò ti consente di utilizzare il pulsante Test senza attivare la logica aziendale.
Limitazioni attuali
Le prenotazioni create tramite API sono prenotazioni manuali. Non creano un ordine Shopify. Se il tuo servizio normalmente utilizza il checkout Shopify, l'API lo ignora. Le e-mail di conferma, gli SMS e la sincronizzazione del calendario funzionano ancora normalmente. Nessun endpoint di disponibilità ancora. L'API corrente ti consente di leggere servizi e prenotazioni e di creare prenotazioni. Non espone le fasce orarie disponibili. Per verificare se una fascia oraria è disponibile, provare a creare la prenotazione e gestire la risposta 422 se la fascia oraria è occupata. Un endpoint di disponibilità potrebbe essere aggiunto in un aggiornamento futuro. Ancora nessun aggiornamento o eliminazione degli endpoint. Puoi creare e leggere le prenotazioni, ma non puoi aggiornarle, riprogrammarle o annullarle tramite API. Gestisci queste azioni tramite l'amministratore Cowlendar per ora. Si applicano limiti di velocità. Se invii troppe richieste in un breve periodo, l'API restituisce 429. Per operazioni di massa come l'esportazione di tutte le prenotazioni, aggiungi un breve ritardo tra le richieste impaginate, ad esempio da 200 a 500 ms. I segreti del webhook vengono visualizzati solo una volta. Se perdi il segreto della firma, elimina l'endpoint e creane uno nuovo per ottenere un nuovo segreto. I webhook richiedono HTTPS. Gli endpoint HTTP non sono accettati.
Domande frequenti
Posso utilizzare l'API Cowlendar con Zapier o Make senza scrivere codice?
SÌ. Per i webhook, che ricevono eventi da Cowlendar, crea un trigger "Custom Webhook" in Zapier o Make, copia l'URL che ti forniscono e aggiungilo come endpoint in Cowlendar Settings > Webhooks. Ogni evento di prenotazione attiva la tua automazione. Per l'API, che invia richieste a Cowlendar, utilizza l'azione "HTTP Request" in Zapier o Make per chiamare qualsiasi endpoint API Cowlendar con il tuo token Bearer.
Le prenotazioni create tramite l'API inviano email ai clienti?
SÌ. Le prenotazioni create tramite API vengono trattate esattamente come prenotazioni manuali. Le e-mail di conferma, le notifiche SMS e la sincronizzazione del calendario si attivano automaticamente, proprio come farebbero per una prenotazione effettuata tramite il pannello di amministrazione.
Cosa succede se il mio endpoint webhook non funziona?
Cowlendar ritenta le consegne non riuscite 6 volte in circa 3 giorni. Dopo circa 20 errori consecutivi, l'endpoint viene disabilitato automaticamente e ricevi un'e-mail. Riattivalo da Settings > Webhooks una volta ripristinato il server.
Posso avere più endpoint webhook?
SÌ. Creane quanti ne hai bisogno, ciascuno con selezioni di eventi e URL diversi. Ad esempio, un endpoint per il tuo CRM che ascolta booking.created e booking.canceled e un altro per il tuo analytics pipeline che ascolta tutti gli eventi.
Posso revocare un token API? SÌ. Vai a Settings > Public API e fai clic sul pulsante Revoke . Il token smette di funzionare immediatamente. Esiste un sandbox o un ambiente di staging?
Non attualmente. Utilizza il pulsante Test webhook.ping per convalidare la configurazione del tuo webhook senza creare prenotazioni reali. Per il test dell'API, ti consigliamo di creare un servizio di test nel tuo amministratore Cowlendar da utilizzare solo per lo sviluppo.
Dov'è il riferimento API completo?
La documentazione API interattiva con tutti gli schemi, i parametri e gli esempi di risposta si trova su https://app.cowlendar.com/public-api/docs