Si ton activité repose sur les réservations, tu as sans doute besoin que tes données de réservation vivent à plusieurs endroits. Ton CRM doit savoir quand un nouveau client réserve. Ton ERP a besoin des données de revenu. Ton outil marketing doit déclencher un e-mail de bienvenue. Et ton app personnalisée peut avoir besoin de créer des réservations de façon programmatique.
LâAPI publique et les webhooks de Cowlendar rendent tout cela possible. LâAPI te permet de lire tes services et tes rĂ©servations, et de crĂ©er de nouvelles rĂ©servations depuis nâimporte quel systĂšme externe. Les webhooks envoient des notifications en temps rĂ©el Ă ton serveur chaque fois quâil se passe quelque chose: une rĂ©servation est créée, confirmĂ©e, annulĂ©e, reprogrammĂ©e, ou un Ă©vĂ©nement dâabonnement se produit.
Ces deux fonctionnalités sont actuellement en Beta.
Que peux-tu construire avec lâAPI de Cowlendar?
Voici des cas dâintĂ©gration rĂ©els que les marchands Cowlendar mettent dĂ©jĂ en place:
Synchroniser chaque rĂ©servation avec HubSpot, Salesforce ou nâimporte quel CRM. Quand un client rĂ©serve une coupe, un cours de yoga ou une location, un webhook part immĂ©diatement avec toutes les donnĂ©es de rĂ©servation: nom, e-mail, tĂ©lĂ©phone, service, date, prix, membre dâĂ©quipe. Ton CRM crĂ©e ou met Ă jour le contact automatiquement, sans saisie manuelle.
CrĂ©er ta propre page de rĂ©servation ou ton app mobile. Utilise lâAPI pour rĂ©cupĂ©rer tes services, y compris les durĂ©es, les membres dâĂ©quipe, les Ă©quipements et les catĂ©gories de participants, puis crĂ©er des rĂ©servations depuis ton propre frontend. La rĂ©servation dĂ©clenche les mĂȘmes e-mails de confirmation, notifications SMS et synchronisations Google Calendar quâune rĂ©servation effectuĂ©e via le widget Cowlendar.
Automatiser des workflows avec Zapier ou Make sans code. Pointe un webhook Cowlendar vers lâURL de webhook fournie par Zapier ou Make. Chaque Ă©vĂ©nement de rĂ©servation devient un dĂ©clencheur. Envoie une notification Slack quand quelquâun rĂ©serve, ajoute une ligne dans Google Sheets, crĂ©e une carte Trello ou lance une sĂ©quence e-mail dans Klaviyo ou Mailchimp.
Envoyer les donnĂ©es de rĂ©servation dans ton outil de reporting ou de BI. Utilise lâendpoint List Bookings pour rĂ©cupĂ©rer toutes les rĂ©servations sur une plage de dates, filtrĂ©es par statut, service, prĂ©sence ou e-mail client. Exporte ensuite ces donnĂ©es vers ton data warehouse, Google Sheets ou Airtable pour crĂ©er des tableaux de bord sur mesure.
Lier les rĂ©servations aux crĂ©dits dâabonnement. Quand tu crĂ©es une rĂ©servation via lâAPI, tu peux passer un subscription_id pour dĂ©duire automatiquement un crĂ©dit du plan du client, par exemple un pass yoga de 10 cours.
Connecter les rĂ©servations POS ou admin Ă des systĂšmes externes. Les webhooks se dĂ©clenchent pour toutes les sources de rĂ©servation: le widget storefront, le panneau admin, le POS et lâAPI elle-mĂȘme. Rien ne tombe entre les mailles du filet.
Authentification
Chaque requĂȘte API nĂ©cessite un Bearer token gĂ©nĂ©rĂ© depuis ton admin Cowlendar. Les tokens ont un accĂšs complet en lecture et en Ă©criture aux rĂ©servations et aux services de ta boutique. Traite-les comme des mots de passe: ne les commit pas dans ton code et ne les partage pas dans des espaces publics.
Comment générer ton token API
Ătape 1: Dans ton admin Shopify, ouvre lâapp Cowlendar.
Ătape 2: Va dans Settings > Public API.
Ătape 3: Clique sur Generate token.
Ătape 4: Donne Ă ton token un nom descriptif pour pouvoir le reconnaĂźtre plus tard, par exemple "HubSpot sync", "Zapier" ou "Mobile app".
Ătape 5: Clique sur Generate. Cowlendar te montre le token complet une seule fois. Copie-le tout de suite et stocke-le dans un endroit sĂ»r, comme un gestionnaire de mots de passe ou les variables dâenvironnement de ton serveur.
Tu peux rĂ©voquer nâimporte quel token Ă tout moment depuis la mĂȘme page. La rĂ©vocation coupe immĂ©diatement toutes les requĂȘtes API qui lâutilisent.
Utiliser ton token
Inclue le token dans le header Authorization de chaque requĂȘte API:
Authorization: Bearer YOUR_TOKEN_HERE
Exemple avec curl:
curl https://app.cowlendar.com/public-api/v1/services -H "Authorization: Bearer YOUR_TOKEN_HERE"
Si le token manque ou sâil est invalide, lâAPI renvoie 401.
Endpoints API
Base URL: https://app.cowlendar.com
Lister les services
GET /public-api/v1/services
Renvoie tous les services non archivĂ©s de ta boutique. Utilise cet endpoint pour rĂ©cupĂ©rer tes IDs de service, types, durĂ©es, membres dâĂ©quipe, Ă©quipements et catĂ©gories de participants avant de crĂ©er des rĂ©servations via lâAPI.
Query parameters:
limit (optionnel, 1-100): nombre de résultats par page.
cursor (optionnel): curseur de pagination issu dâune rĂ©ponse prĂ©cĂ©dente.
is_active (optionnel, true ou false): filtre les services actifs ou inactifs.
type (optionnel): filtre par type de service. Valeurs: classic-checkout, classic-no-checkout, multiday-checkout, multiday-no-checkout, multislot-checkout, multislot-no-checkout, fullday-checkout, fullday-no-checkout.
q (optionnel, 120 caractĂšres max): recherche de services par titre.
sort (optionnel): -id par dĂ©faut, les plus rĂ©cents dâabord, id les plus anciens dâabord, title de A Ă Z, -title de Z Ă A.
Exemple de requĂȘte:
curl "https://app.cowlendar.com/public-api/v1/services?is_active=true&limit=10" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Exemple de réponse, raccourci:
{
"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
}
}
Comprendre les champs clĂ©s dâun service
avs_type tâindique comment le service gĂšre sa disponibilitĂ© et dĂ©termine si tu dois passer un teammate_id lors de la crĂ©ation dâune rĂ©servation:
avs_type: "teammates" signifie que chaque membre dâĂ©quipe a son propre planning indĂ©pendant. Quand tu crĂ©es une rĂ©servation, passe un teammate_id depuis le tableau teammates. Si tu ne lâenvoies pas, le propriĂ©taire de la boutique est utilisĂ© comme solution de secours.
avs_type: "service" signifie que le service lui-mĂȘme porte la disponibilitĂ©. Le membre assignĂ© est dĂ©terminĂ© automatiquement Ă partir de lâhoraire de rĂ©servation. Tu peux quand mĂȘme passer un teammate_id pour forcer lâassignation Ă une personne prĂ©cise.
avs_type: "equipment" signifie que la disponibilitĂ© est gĂ©rĂ©e par Ă©quipement. Câest proche du mode service, mais liĂ© Ă la capacitĂ© de lâĂ©quipement.
enable_participants tâindique si le service utilise des catĂ©gories de participants typĂ©es, comme "Adult", "Child" ou "Senior". Quand cette valeur est Ă true, le tableau participants contient ces catĂ©gories avec leurs IDs, leurs minimums, leurs maximums et leurs valeurs par dĂ©faut. Tu as besoin de ces IDs pour crĂ©er des rĂ©servations avec un dĂ©tail prĂ©cis des participants.
equipments contient les ressources rĂ©servables, comme des salles, des terrains, des vĂ©hicules ou des vĂ©los, avec leurs variantes et leurs SKUs. Pour un Ă©quipement avec tracking_type: "same", toutes les unitĂ©s sont interchangeables et tu peux utiliser nâimporte quel SKU, gĂ©nĂ©ralement base. Pour tracking_type: "unique", chaque unitĂ© a son propre SKU et tu dois choisir un variants[].sku.
type tâindique le modĂšle de rĂ©servation. Le suffixe -checkout ou -no-checkout prĂ©cise si le service utilise le checkout Shopify. Le prĂ©fixe indique le modĂšle de planning: classic pour un seul crĂ©neau, multiday pour plusieurs jours comme un sĂ©jour, multislot pour plusieurs crĂ©neaux consĂ©cutifs, et fullday pour une rĂ©servation sur la journĂ©e entiĂšre.
Lister les réservations
GET /public-api/v1/bookings
Renvoie les rĂ©servations de ta boutique. Câest lâendpoint Ă utiliser pour les synchronisations CRM, les exports de reporting et les tableaux de bord personnalisĂ©s.
Query parameters:
limit (optionnel, 1-100): résultats par page.
cursor (optionnel): curseur de pagination.
start (optionnel, date ISO 8601): uniquement les réservations qui commencent à cette date ou aprÚs.
end (optionnel, date ISO 8601): uniquement les réservations qui commencent avant cette date.
service_id (optionnel, tableau dâIDs): filtre un ou plusieurs services.
subscription_id (optionnel, tableau dâIDs): filtre les abonnements.
status (optionnel, tableau): confirmed, pending, declined, canceled.
attendance (optionnel, tableau): booked, pending, arrived, started, completed, no-show, delayed, paid.
customer_email (optionnel): filtre par e-mail client exact.
sort (optionnel): -id par dĂ©faut, plus rĂ©cents dâabord, id plus anciens dâabord, start_date les plus tĂŽt dâabord, -start_date les plus rĂ©cents dâabord.
Exemple: récupérer toutes les réservations confirmées de la semaine prochaine:
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"
Exemple: retrouver toutes les rĂ©servations dâun client donnĂ©:
curl "https://app.cowlendar.com/public-api/v1/[email protected]" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Exemple: exporter tous les no-shows dâun service prĂ©cis:
curl "https://app.cowlendar.com/public-api/v1/bookings?attendance=no-show&service_id=6789abcdef0123456789abcd" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Exemple de réponse, raccourci:
{
"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
}
}
Comprendre les champs clĂ©s dâune rĂ©servation
quantity et unit_quantity fonctionnent ensemble. Pour un service classic, comme une coupe, quantity correspond au nombre de clients présents et unit_quantity vaut 1. Pour un service multiday, comme un séjour, quantity correspond au nombre de chambres et unit_quantity au nombre de nuits. Pour un service multislot, quantity vaut toujours 1 et unit_quantity correspond au nombre de créneaux réservés. Le total des unités facturables est toujours quantity x unit_quantity.
quantity_details est le dĂ©tail fin. Il peut contenir trois types dâentrĂ©es: default pour un compte anonyme, participant pour un participant typĂ© comme "Adult" ou "Child", et equipment pour une ressource rĂ©servable comme "Court 3". Les anciennes rĂ©servations peuvent avoir un tableau vide ici.
confirmation_status peut ĂȘtre confirmed, pending ou declined. Certains services nĂ©cessitent une confirmation manuelle par lâadmin avant quâune rĂ©servation soit active.
attendance suit le cycle de présence du client: booked, pending, arrived, started, completed, no-show, delayed, paid.
order_id correspond Ă lâID de commande Shopify quand la rĂ©servation est passĂ©e par le checkout Shopify. Il vaut null pour les rĂ©servations manuelles et pour les rĂ©servations créées via lâAPI.
subscription_id relie la rĂ©servation Ă un plan dâabonnement, par exemple un pass de 10 cours.
CrĂ©er une rĂ©servation via lâAPI
POST /public-api/v1/bookings
CrĂ©e une rĂ©servation de façon programmatique. Elle est traitĂ©e comme une manual booking, ce qui veut dire quâaucune commande Shopify nâest créée. En revanche, toutes les automatisations habituelles se dĂ©clenchent normalement: e-mails de confirmation au client, e-mails et SMS Ă lâĂ©quipe, et synchronisation calendrier avec Google Calendar ou Outlook.
Cet endpoint est celui à utiliser si tu construis un frontend de réservation personnalisé, une app mobile, une borne, ou si tu importes des réservations depuis un autre systÚme.
Champs requis:
service_id (string): lâID du service, Ă rĂ©cupĂ©rer via List Services.
start_date (date ISO 8601): début de la réservation.
end_date (date ISO 8601): fin de la réservation.
timezone (string): fuseau horaire IANA, par exemple Europe/Paris, America/New_York, Asia/Tokyo.
customer (object): doit contenir au minimum email ou phone. Il peut aussi inclure name, firstname, lastname et locale, qui est le code langue pour les e-mails envoyés au client.
Champs optionnels:
quantity (integer, défaut 1): unités principales réservées. Ignoré si quantity_details est fourni.
unit_quantity (integer, défaut 1): sous-unités par unité principale.
quantity_details (array): détail par type de participant ou équipement. Quand il est fourni, quantity est calculé automatiquement comme la somme.
teammate_id (string): force lâassignation Ă un membre prĂ©cis de lâĂ©quipe.
subscription_id (string): relie cette réservation à un abonnement existant et déduit un crédit.
form_data (object): paires clé ou valeur personnalisées issues de ton formulaire.
price (number, défaut 0): prix total net de la réservation.
currency (string): code ISO 4217 comme USD, EUR ou GBP. Par dĂ©faut, lâAPI utilise la devise de ta boutique.
Exemple: créer une réservation simple
Un client réserve une coupe de 30 minutes avec un coiffeur précis:
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 crĂ©e la rĂ©servation et dĂ©clenche lâe-mail de confirmation, le SMS et lâinvitation calendrier pour le client comme pour le membre dâĂ©quipe.
Exemple: réservation de groupe avec participants typés
Une famille réserve une visite guidée: 3 adultes et 2 enfants avec des prix différents:
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"
}'
Les valeurs participant_id viennent du tableau participants[] du service dans la réponse List Services. Le total de quantity est automatiquement fixé à 5, soit 3 + 2.
Exemple: réservation avec équipement
Un client réserve un court de tennis pendant 1 heure:
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"
}'
Les valeurs equipment_id et equipment_sku viennent du tableau equipments[] du service. Pour un Ă©quipement avec tracking_type: "same", utilise nâimporte quel SKU, en gĂ©nĂ©ral base. Pour tracking_type: "unique", utilise le variants[].sku spĂ©cifique Ă lâunitĂ© voulue.
Exemple: réservation liée à un abonnement
Un membre dâun studio de yoga utilise un crĂ©dit de son pass mensuel de 10 cours:
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âabonnement doit ĂȘtre active, appartenir au mĂȘme service et avoir remaining_credits > 0. Cowlendar dĂ©crĂ©mente automatiquement le crĂ©dit restant et envoie lâe-mail de mise Ă jour dâabonnement au client.
Codes dâerreur
400 Erreur de validation. Un champ requis manque ou un champ a un mauvais format. Le body dâerreur tâindique quel champ pose problĂšme.
401 Token manquant ou invalide.
403 Boutique inactive ou accĂšs restreint.
404 Service introuvable.
422 RĂ©servation rejetĂ©e. Le crĂ©neau est dĂ©jĂ pris, le membre dâĂ©quipe nâest pas disponible, lâĂ©quipement a atteint sa capacitĂ©, ou lâabonnement nâa plus de crĂ©dits. VĂ©rifie le message dâerreur pour connaĂźtre la raison prĂ©cise.
429 Rate limit dĂ©passĂ©. Espace un peu tes requĂȘtes et rĂ©essaie aprĂšs un court instant.
Pagination
Tous les endpoints de liste utilisent une pagination par curseur. Quand la rĂ©ponse contient "has_more": true, passe la valeur next_cursor dans le paramĂštre cursor de la requĂȘte suivante:
curl "https://app.cowlendar.com/public-api/v1/bookings?cursor=eyJpZCI6IjY3ODlhYmNkIn0=&limit=50" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Lâordre de tri est conservĂ© dâune page Ă lâautre. La taille maximale dâune page est de 100 rĂ©sultats.
Webhooks: notifications de réservation en temps réel
Alors que lâAPI te permet dâaller chercher les donnĂ©es Ă la demande, les webhooks te les poussent immĂ©diatement. Chaque fois quâune rĂ©servation est créée, confirmĂ©e, annulĂ©e ou reprogrammĂ©e, Cowlendar envoie un HTTP POST signĂ© vers lâURL que tu as configurĂ©e.
Les webhooks sont essentiels pour les intĂ©grations en temps rĂ©el: synchros CRM, notifications Slack, sĂ©quences e-mail automatisĂ©es, dashboards live, ou tout workflow qui doit rĂ©agir dĂšs quâun Ă©vĂ©nement de rĂ©servation se produit.
Configurer un endpoint webhook
Ătape 1: Dans ton admin Shopify, ouvre lâapp Cowlendar.
Ătape 2: Va dans Settings > Webhooks.
Ătape 3: Clique sur Add endpoint.
Ătape 4: Saisis un nom pour ton endpoint, par exemple "HubSpot CRM", "Zapier" ou "Analytics pipeline".
Ătape 5: Colle ton HTTPS URL. LâURL doit impĂ©rativement utiliser HTTPS. Si tu utilises Zapier ou Make, colle lâURL de webhook quâils te donnent.
Ătape 6: Coche les Ă©vĂ©nements que tu veux recevoir. Tu peux choisir nâimporte quelle combinaison.
Ătape 7: Clique sur Create.
Cowlendar affiche un signing secret qui commence par whsec_. Copie-le immĂ©diatement et conserve-le en lieu sĂ»r. Ce secret nâest affichĂ© quâune seule fois et tu en auras besoin pour vĂ©rifier les requĂȘtes webhook entrantes sur ton serveur.
Tu peux modifier plus tard nâimporte quel endpoint pour changer lâURL ou activer et dĂ©sactiver certains Ă©vĂ©nements:
Les 8 événements webhook
booking.created se dĂ©clenche quand une nouvelle rĂ©servation est créée depuis nâimporte quelle source: le widget storefront, le panneau admin, le POS ou la Public API.
booking.confirmed se dĂ©clenche quand une rĂ©servation en attente est confirmĂ©e manuellement par lâadmin.
booking.declined se déclenche quand une réservation en attente est refusée.
booking.canceled se dĂ©clenche quand une rĂ©servation est annulĂ©e par le client ou par lâadmin.
booking.rescheduled se dĂ©clenche quand la date dâune rĂ©servation ou les membres dâĂ©quipe assignĂ©s changent.
booking.attendance_changed se dĂ©clenche quand le statut de prĂ©sence change, par exemple de "booked" Ă "completed" ou lorsquâil est marquĂ© "no-show".
subscription.created se déclenche quand un nouvel abonnement récurrent est créé.
subscription.billing_failed se dĂ©clenche quand une tentative de facturation dâabonnement Ă©choue. AprĂšs 3 Ă©checs consĂ©cutifs, le contrat dâabonnement est automatiquement annulĂ© sur Shopify et le statut local passe Ă canceled.
Ă quoi ressemble le payload dâun webhook
Chaque livraison webhook arrive sous forme de HTTP POST avec un body JSON. Voici un exemple complet de booking.created:
{
"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"
}
}
Le champ data contient lâobjet de rĂ©servation ou dâabonnement complet, exactement dans la mĂȘme structure que la rĂ©ponse API.
Pour les Ă©vĂ©nements subscription.billing_failed, le payload inclut en plus un objet previous contenant le failure_count avant la tentative actuelle. Cela te permet de suivre lâescalade, par exemple en envoyant un e-mail urgent aprĂšs le deuxiĂšme Ă©chec.
Headers présents sur chaque livraison webhook
X-Cowlendar-Event-Id est lâID unique de lâĂ©vĂ©nement. Cowlendar rĂ©utilise le mĂȘme ID lorsquâil retente une livraison Ă©chouĂ©e. Utilise-le pour dĂ©dupliquer de ton cĂŽtĂ©.
X-Cowlendar-Event-Type est la chaĂźne du type dâĂ©vĂ©nement, par exemple booking.created.
X-Cowlendar-Timestamp est le timestamp Unix en secondes au moment oĂč Cowlendar a signĂ© la requĂȘte.
X-Cowlendar-Signature est la signature HMAC-SHA256 au format t=<timestamp>,v1=<hex>.
Vérifier la signature du webhook
Chaque requĂȘte webhook est signĂ©e afin que tu puisses confirmer quâelle vient bien de Cowlendar et quâelle nâa pas Ă©tĂ© altĂ©rĂ©e. La signature est calculĂ©e comme HMAC-SHA256(secret, timestamp + "." + raw_body) Ă lâaide du signing secret de ton endpoint.
Tu devrais aussi rejeter les requĂȘtes dont le timestamp date de plus de 5 minutes afin dâĂ©viter les 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";
Tester ton webhook avant la mise en production
Chaque endpoint webhook dispose dâun bouton Test dans lâadmin. Quand tu cliques dessus, il envoie un Ă©vĂ©nement synthĂ©tique webhook.ping Ă ton URL. Cet Ă©vĂ©nement utilise la mĂȘme signature, les mĂȘmes headers et le mĂȘme comportement de retry que les Ă©vĂ©nements rĂ©els, mais avec un payload de test sans risque:
{
"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."
}
}
Utilise-le pour confirmer que Cowlendar peut joindre ton URL, que DNS, HTTPS et le firewall fonctionnent bien, et pour valider ton code de vĂ©rification de signature HMAC sans crĂ©er de vraie rĂ©servation. Une vraie rĂ©servation enverrait des e-mails Ă un client, crĂ©erait des Ă©vĂ©nements calendrier et pourrait mĂȘme dĂ©clencher une commande Shopify.
AprÚs avoir cliqué sur Test, vérifie le bouton See deliveries pour voir le code HTTP renvoyé par ton endpoint. Un 200 vert signifie que tout fonctionne.
Comportement de retry et désactivation automatique
Si ton endpoint renvoie une erreur 5xx, un 429 ou un timeout réseau, Cowlendar retente selon ce calendrier:
AprĂšs 1 minute, aprĂšs 5 minutes, aprĂšs 25 minutes, aprĂšs 2 heures, aprĂšs 10 heures et aprĂšs 50 heures. Cela fait 6 tentatives au total sur environ 3 jours.
AprĂšs environ 20 Ă©checs consĂ©cutifs, tous Ă©vĂ©nements confondus, lâendpoint est automatiquement dĂ©sactivĂ© et Cowlendar envoie un e-mail au propriĂ©taire de la boutique. Tu pourras le rĂ©activer dans Settings > Webhooks une fois ton serveur revenu en ligne.
Le mĂȘme X-Cowlendar-Event-Id est rĂ©utilisĂ© lors des retries. Stocke toujours les IDs dĂ©jĂ traitĂ©s et ignore les doublons.
Toute rĂ©ponse 2xx est considĂ©rĂ©e comme un succĂšs. Tu nâas pas besoin de renvoyer un body ou un content type particulier.
Bonnes pratiques pour les webhooks
Réponds en moins de 10 secondes. Si ton handler doit faire du travail lourd, comme appeler une autre API, écrire en base ou lancer une opération complexe, fais-le de maniÚre asynchrone. Accepte immédiatement le webhook avec un 200, puis traite-le dans un background job ou une file.
DĂ©duplique avec lâevent ID. Stocke chaque X-Cowlendar-Event-Id traitĂ©. Si tu reçois le mĂȘme ID une nouvelle fois, saute-le. Cowlendar rĂ©essaie avec le mĂȘme ID en cas dâĂ©chec.
Utilise une comparaison en temps constant pour les signatures. crypto.timingSafeEqual en Node.js, hmac.compare_digest en Python et hash_equals en PHP. Une comparaison de chaßnes classique comme === est vulnérable aux timing attacks.
Ne fais pas de retry de ton cÎté. Cowlendar gÚre déjà les retries. Si ton serveur a un souci temporaire, renvoie 5xx et Cowlendar reviendra selon son propre planning.
GĂšre webhook.ping explicitement. Renvoie 200 immĂ©diatement pour les Ă©vĂ©nements ping. Cela te permet dâutiliser le bouton Test sans dĂ©clencher ta logique mĂ©tier.
Limites actuelles
Les rĂ©servations créées via lâAPI sont des manual bookings. Elles ne crĂ©ent pas de commande Shopify. Si ton service utilise normalement le checkout Shopify, lâAPI le contourne. Les e-mails de confirmation, les SMS et la synchronisation calendrier fonctionnent quand mĂȘme normalement.
Pas encore dâendpoint de disponibilitĂ©. LâAPI actuelle te permet de lire les services, lire les rĂ©servations et crĂ©er des rĂ©servations. Elle nâexpose pas les crĂ©neaux disponibles. Pour savoir si un crĂ©neau est libre, tente la crĂ©ation de rĂ©servation et gĂšre la rĂ©ponse 422 si le crĂ©neau est dĂ©jĂ pris. Un endpoint de disponibilitĂ© pourrait arriver plus tard.
Pas encore dâendpoint de mise Ă jour ou de suppression. Tu peux crĂ©er et lire des rĂ©servations, mais pas les modifier, les reprogrammer ni les annuler via lâAPI. Pour lâinstant, ces actions doivent ĂȘtre gĂ©rĂ©es dans lâadmin Cowlendar.
Des rate limits sâappliquent. Si tu envoies trop de requĂȘtes dans une fenĂȘtre courte, lâAPI renvoie 429. Pour les opĂ©rations volumineuses, comme lâexport complet des rĂ©servations, ajoute un lĂ©ger dĂ©lai entre les requĂȘtes paginĂ©es, par exemple 200 Ă 500 ms.
Les webhook secrets ne sont affichĂ©s quâune seule fois. Si tu perds ton signing secret, supprime lâendpoint et recrĂ©e-en un nouveau pour obtenir un secret neuf.
Les webhooks exigent HTTPS. Les endpoints HTTP ne sont pas acceptés.
FAQ
Puis-je utiliser lâAPI Cowlendar avec Zapier ou Make sans Ă©crire de code?
Oui. Pour les webhooks, câest-Ă -dire recevoir des Ă©vĂ©nements depuis Cowlendar, crĂ©e un trigger "Custom Webhook" dans Zapier ou Make, copie lâURL quâils te donnent et ajoute-la comme endpoint dans Cowlendar Settings > Webhooks. Chaque Ă©vĂ©nement de rĂ©servation dĂ©clenchera ton automatisation. Pour lâAPI, donc envoyer des requĂȘtes vers Cowlendar, utilise lâaction "HTTP Request" dans Zapier ou Make pour appeler nâimporte quel endpoint Cowlendar avec ton Bearer token.
Les rĂ©servations créées via lâAPI envoient-elles des e-mails aux clients?
Oui. Les rĂ©servations créées via lâAPI sont traitĂ©es exactement comme des rĂ©servations manuelles. Les e-mails de confirmation, les notifications SMS et la synchronisation calendrier se dĂ©clenchent automatiquement, comme pour une rĂ©servation créée depuis le panneau admin.
Que se passe-t-il si mon endpoint webhook tombe?
Cowlendar retente les livraisons Ă©chouĂ©es 6 fois sur environ 3 jours. AprĂšs une vingtaine dâĂ©checs consĂ©cutifs, lâendpoint est automatiquement dĂ©sactivĂ© et tu reçois un e-mail. RĂ©active-le dans Settings > Webhooks quand ton serveur est de nouveau stable.
Puis-je avoir plusieurs endpoints webhook?
Oui. Tu peux en crĂ©er autant que nĂ©cessaire, chacun avec sa propre sĂ©lection dâĂ©vĂ©nements et sa propre URL. Par exemple, un endpoint pour ton CRM Ă©coutant booking.created et booking.canceled, et un autre pour ton pipeline analytics Ă©coutant tous les Ă©vĂ©nements.
Puis-je révoquer un token API?
Oui. Va dans Settings > Public API et clique sur le bouton Revoke. Le token cesse de fonctionner immédiatement.
Existe-t-il un sandbox ou un environnement de staging?
Pas pour le moment. Utilise le bouton Test de webhook.ping pour valider ta configuration webhook sans crĂ©er de vraies rĂ©servations. Pour les tests API, nous te recommandons de crĂ©er un service de test dans ton admin Cowlendar et de lâutiliser uniquement pour le dĂ©veloppement.
OĂč trouver la rĂ©fĂ©rence API complĂšte?
La documentation API interactive, avec tous les schémas, paramÚtres et exemples de réponse, se trouve sur https://app.cowlendar.com/public-api/docs