🔗Integrations

🔗 Connecte Cowlendar Ă  ton CRM, ERP ou app personnalisĂ©e (API publique et webhooks)

Utilise l’API publique et les webhooks de Cowlendar pour synchroniser les rĂ©servations avec ton CRM, ton ERP ou tes outils

27 min read
2 views
Updated May 22, 2026

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.

â„čInfo
La référence API interactive complÚte, avec tous les schémas et les exemples de réponse, se trouve sur https://app.cowlendar.com/public-api/docs

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.

⚠Warning
Calcule toujours la signature sur le RAW request body, c’est-Ă -dire les octets exacts envoyĂ©s par Cowlendar, et non sur un objet JSON parsĂ© puis re-sĂ©rialisĂ©. Le parsing et la re-sĂ©rialisation peuvent changer les espaces ou l’ordre des clĂ©s, ce qui casse la signature.

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

Cowlendar
Ready to use Cowlendar?
Open the app directly in your Shopify store.