# Webhooks : envoyer vos réservations vers Airtable

> Version web : https://www.calenoo.ch/fr/help/webhooks
> Dernière mise à jour : 2026-11-05
> [Français](https://www.calenoo.ch/fr/help/webhooks.md) · [Deutsch](https://www.calenoo.ch/de/help/webhooks.md) · [English](https://www.calenoo.ch/en/help/webhooks.md) · [Italiano](https://www.calenoo.ch/it/help/webhooks.md)

Un webhook envoie automatiquement chaque réservation, déplacement ou annulation de vos pages Calenoo vers un autre outil : une base Airtable, ou tout outil capable de recevoir un webhook HTTPS (Zapier, Make, n8n…). Cette page explique comment le régler, ce que Calenoo envoie et ce qui se passe en cas de panne.

## À quoi servent les webhooks

Un webhook envoie automatiquement à l'adresse web de votre choix les informations de chaque réservation, de chaque déplacement et de chaque annulation, dès qu'ils ont lieu.

L'outil qui les reçoit peut les ranger dans un tableau, prévenir quelqu'un ou lancer d'autres actions. Calenoo se limite à l'envoi : la suite se règle dans cet outil.

Le réglage se fait une seule fois, dans vos [Réglages](https://www.calenoo.ch/fr/app/account#webhooks), et vaut pour toutes vos pages de réservation. Vous pouvez enregistrer 3 adresses au plus.

Il suffit d'un compte Calenoo connecté : la rubrique « Webhooks » se trouve dans vos Réglages, à côté des pages de réservation.

Les webhooks se combinent avec la validation des demandes et les séances de groupe, deux autres fonctions avancées des pages de réservation. Voir le [guide des fonctions avancées](https://www.calenoo.ch/fr/guides/reservation-validation-groupes-webhooks).

## Pas à pas : Airtable

Ces étapes suivent la documentation d'Airtable sur le déclencheur « When webhook received » ([source](https://support.airtable.com/docs/en/when-webhook-received-trigger), lue le 2 novembre 2026). Si l'écran d'Airtable diffère de ce qui est décrit ici, sa page d'aide fait foi.

1. Dans votre base Airtable, ouvrez « Automations » et créez une automatisation.
2. Ajoutez un déclencheur et choisissez « When webhook received ». Airtable affiche une adresse web unique : copiez-la.
3. Dans Calenoo, ouvrez [Réglages › Webhooks](https://www.calenoo.ch/fr/app/account#webhooks), collez l'adresse dans « Adresse de réception » et cliquez sur « Ajouter ».
4. Laissez la case « Signer les envois » décochée : Airtable ne sait pas vérifier une signature.
5. Cliquez sur « Envoyer un test ». Calenoo envoie un exemple de réservation complet, sans champ vide, et affiche « Test reçu » quand Airtable accepte l'envoi.
6. Dans Airtable, lancez le test du déclencheur (bouton « Run test »). Airtable utilise l'envoi de test comme exemple pour les étapes suivantes.
7. Ajoutez les actions qui utiliseront ces données et associez-y les champs reçus. Pour retrouver un rendez-vous déplacé ou annulé, utilisez `booking.id` comme identifiant.
8. Testez chaque étape, puis activez l'automatisation (de OFF à ON). Dès lors, chaque réservation de vos pages part vers Airtable.

### Ce que dit Airtable de ce déclencheur

Relevé sur la [page d'aide d'Airtable](https://support.airtable.com/docs/en/when-webhook-received-trigger) le 2 novembre 2026. Ces limites sont celles d'Airtable et peuvent changer.

- Seules les requêtes POST sont acceptées, avec un en-tête `Content-Type` à `application/json` pour du JSON. C'est ce que Calenoo envoie.
- Un envoi ne doit pas dépasser 100 kb, et le déclencheur accepte au plus cinq requêtes par seconde.
- Chaque envoi qui lance une automatisation compte dans les limites d'exécutions de votre plan Airtable. Le déclencheur est proposé dans tous les types de plan, avec des limites qui varient.
- Airtable ne gère pas la vérification de signature : gardez la signature de Calenoo désactivée pour Airtable.
- Toute personne qui connaît l'adresse du déclencheur peut lancer votre automatisation. Ne la partagez pas.

## Les événements envoyés

Calenoo envoie tous les événements du tableau, sans sélection possible : votre outil lit le champ `event` et garde ceux qui l'intéressent.

| Événement | Quand il est envoyé |
| --- | --- |
| `booking.created` | Un invité réserve sur une page sans validation. |
| `booking.requested` | Un invité envoie une demande sur une page « sur validation » (`status` vaut `pending`, `request_expires_at` donne l'échéance). |
| `booking.accepted` | Vous acceptez la demande (`status` passe à `confirmed`, `previous_status` vaut `pending` ; le lien de visioconférence est ajouté s'il y en a un). |
| `booking.declined` | Vous refusez la demande (`status` vaut `declined`). |
| `booking.expired` | La demande n'a pas reçu de réponse avant l'échéance (`status` vaut `expired`). |
| `booking.rescheduled` | L'invité déplace son rendez-vous ou sa demande (`previous_start` donne l'ancien début ; une demande reste `pending`). |
| `booking.cancelled` | L'invité annule son rendez-vous ou retire sa demande, ou vous annulez depuis Calenoo (`previous_status` donne l'état d'avant). |

Chaque envoi concerne un seul rendez-vous. Pour une page de groupe (plusieurs places par créneau), chaque participant fait l'objet d'un envoi distinct, avec son propre `booking.id` et le `booking.session_id` de sa séance, le même pour tous ses participants.

## Le format des données

Chaque envoi est une requête `POST` dont le corps est du JSON (version 1). Voici un exemple. Le bouton « Envoyer un test » envoie le même format, avec `test` à `true` et tous les champs remplis.

```json
{
  "version": 1,
  "event": "booking.created",
  "event_id": "5d0c6b0e-6b0a-4a52-9d5c-2f1d3c9a7e11",
  "occurred_at": "2026-11-02T18:00:00Z",
  "test": false,
  "booking": {
    "id": "b3f1c2a4-8d7e-4c6b-9a10-5e2f4d8c1a77",
    "status": "confirmed",
    "previous_status": null,
    "start": "2026-11-07T09:00:00Z",
    "end": "2026-11-07T10:00:00Z",
    "start_local": "2026-11-07T10:00:00+01:00",
    "end_local": "2026-11-07T11:00:00+01:00",
    "timezone": "Europe/Zurich",
    "duration_minutes": 60,
    "previous_start": null,
    "location": "Bern",
    "meeting_url": null,
    "language": "de",
    "request_expires_at": null,
    "seats": null,
    "session_id": null,
    "invitee": {
      "name": "Jeanne Fit",
      "email": "jeanne@example.ch",
      "note": "Looking forward to it"
    }
  },
  "page": {
    "id": "9a4e7d52-3c1b-4f08-8e66-0b7d2a5c9f33",
    "slug": "personal-training",
    "title": "Personal Training"
  }
}
```

### Les champs principaux

- `event` : l'événement (voir le tableau ci-dessus). `event_id` : un identifiant commun à toutes les adresses qui reçoivent le même événement. `occurred_at` : l'heure de l'événement, en heure universelle (UTC).
- `booking.id` : l'identifiant du rendez-vous. C'est la clé à utiliser pour le retrouver.
- `booking.status` et `booking.previous_status` : l'état du rendez-vous et son état précédent (`null` s'il n'y en a pas).
- `booking.start` et `booking.end` : début et fin en heure universelle (UTC). `booking.start_local` et `booking.end_local` : les mêmes, avec l'heure et le décalage du fuseau de la page (`booking.timezone`). `booking.duration_minutes` : la durée en minutes.
- `booking.previous_start` : l'ancien début, pour un déplacement ; `null` sinon.
- `booking.location`, `booking.language` et `booking.meeting_url` : le lieu de la page, la langue de la réservation et le lien de visioconférence (`null` s'il n'y en a pas). Une demande en attente n'a pas encore de lien : il arrive avec `booking.accepted`.
- `booking.request_expires_at` : l'échéance d'une demande à valider (48 heures au plus, jamais après le début du rendez-vous), `null` sinon. Elle reste renseignée après la décision.
- `booking.seats` : le nombre de places par créneau d'une page de groupe, `null` pour un rendez-vous individuel.
- `booking.session_id` : l'identifiant de la séance d'une page de groupe, le même pour tous ses participants, `null` pour un rendez-vous individuel. Il reste renseigné après une annulation ; un déplacement donne celui de la nouvelle séance.
- `booking.invitee` : le nom, l'adresse e-mail et le message (`note`, `null` s'il n'y en a pas) de l'invité.
- `page` : l'identifiant, l'adresse courte (`slug`) et le titre de la page de réservation.

### Les en-têtes

- `Content-Type: application/json`
- `User-Agent: Calenoo-Webhook/1`
- `Calenoo-Event` : l'événement, par exemple `booking.created`.
- `Calenoo-Delivery` : l'identifiant de l'envoi. Il reste identique à chaque nouvel essai : servez-vous-en pour ignorer un doublon.
- `Calenoo-Signature` : présent seulement si la signature est activée (voir ci-dessous).

La structure de la version 1 ne change pas : toute modification passerait par une version 2.

## Vérifier la signature

Cette étape est facultative. Si vous cochez « Signer les envois », Calenoo ajoute l'en-tête `Calenoo-Signature` à chaque envoi : votre outil peut ainsi vérifier que l'envoi vient bien de Calenoo et qu'il n'a pas été modifié.

L'en-tête a la forme `t=<secondes>,v1=<signature>`. `t` est l'heure d'envoi en secondes (heure Unix). `v1` est un HMAC-SHA256 en hexadécimal, calculé avec votre secret sur le texte `t.corps` : `t`, un point, puis le corps de la requête tel qu'il a été reçu.

Le secret commence par `whsec_` : utilisez-le en entier, préfixe compris. Calenoo ne l'affiche qu'une seule fois, à sa création : copiez-le aussitôt. Si vous le perdez, créez-en un autre avec « Nouveau secret ».

Voici un exemple en JavaScript (Node.js). Calculez toujours la signature sur le texte brut reçu, avant tout `JSON.parse`. Refusez aussi un envoi dont `t` est trop ancien : ici, plus de 5 minutes.

```js
import crypto from "node:crypto";

function isValidSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  const received = Buffer.from(parts.v1 ?? "", "hex");
  const computed = Buffer.from(expected, "hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
```

Node.js met les noms d'en-têtes en minuscules : l'en-tête se lit avec `req.headers['calenoo-signature']`.

## En cas de panne

Un envoi réussit quand votre outil répond par un code 2xx en moins de 10 secondes. Calenoo ne lit jamais le contenu de la réponse et ne suit aucune redirection : une réponse 3xx compte comme un échec.

- Réponse 408, 425, 429 ou 5xx, délai dépassé ou coupure réseau : Calenoo réessaie, 8 essais au plus (le premier compris), après 1, 2, 5, 15 et 30 minutes, puis 1 heure et 2 heures. Si la réponse contient un en-tête `Retry-After` (en secondes) plus long que cette attente, Calenoo l'attend, jusqu'à 6 heures au plus.
- Toute autre réponse 4xx (404 ou 401, par exemple) : échec définitif, sans nouvel essai.
- Les envois d'une même adresse partent dans l'ordre : un envoi en attente d'un nouvel essai retient les suivants.
- Après 5 envois de suite en échec définitif, Calenoo désactive l'adresse et vous envoie un e-mail. Un envoi réussi remet le compteur à zéro.
- Les envois manqués ne sont pas renvoyés quand vous réactivez l'adresse. Une fois le problème réglé, cliquez sur « Réactiver » dans [Réglages › Webhooks](https://www.calenoo.ch/fr/app/account#webhooks).

Un même envoi peut arriver deux fois, par exemple si votre outil l'a bien reçu mais que sa réponse s'est perdue. Comme `Calenoo-Delivery` est identique à chaque essai, votre outil peut ignorer un doublon.

Le journal « Derniers envois » de [Réglages › Webhooks](https://www.calenoo.ch/fr/app/account#webhooks) montre l'événement, le code de réponse et l'état de chaque envoi. Il est conservé 30 jours, sans le contenu des envois.

## Sécurité et données personnelles

- Calenoo envoie uniquement en `https`, sur le port 443. Une adresse avec identifiant ou mot de passe, ou sur un autre port, est refusée.
- Les adresses privées ou locales (réseau interne, `localhost`…) sont refusées à l'enregistrement, puis revérifiées avant chaque envoi.
- Jamais dans les envois : les liens et jetons pour gérer le rendez-vous, l'empreinte IP, votre adresse e-mail et les références de votre agenda.
- Le contenu de chaque envoi est effacé dès que l'envoi est terminé.
- Vous gardez la main : vous pouvez mettre une adresse en pause, la signer, changer son secret ou la supprimer à tout moment.

Chaque envoi contient des données personnelles : le nom, l'adresse e-mail et le message de l'invité, avec l'horaire du rendez-vous. Elles vont à l'outil que vous avez choisi, qui les traite sous sa propre responsabilité et selon ses propres conditions. Voir la [politique de confidentialité](https://www.calenoo.ch/fr/legal/privacy).

## Questions fréquentes

### Faut-il un plan Airtable payant ?

Airtable indique que le déclencheur « When webhook received » est proposé dans tous les types de plan, avec des limites qui varient. Chaque envoi qui lance une automatisation compte dans les limites d'exécutions de votre plan : vérifiez-les dans votre compte Airtable ([source](https://support.airtable.com/docs/en/when-webhook-received-trigger)).

### Mon outil est en panne : perd-on des réservations ?

Non, les réservations restent dans Calenoo. Seul l'envoi vers votre outil est concerné : Calenoo réessaie jusqu'à 8 fois, avec des attentes de 1 minute à 2 heures. Après 5 envois de suite en échec définitif, l'adresse est désactivée et les envois manqués ne sont pas renvoyés.

### Puis-je recevoir seulement certains événements ?

Non : Calenoo envoie tous les événements. Votre outil lit le champ `event` (ou l'en-tête `Calenoo-Event`) et garde ceux qu'il veut traiter.

### Comment éviter les doublons dans mon tableau ?

Utilisez `booking.id` comme clé : un déplacement ou une annulation concerne le même rendez-vous et peut donc mettre à jour la même ligne. L'en-tête `Calenoo-Delivery` identifie chaque envoi et reste identique à chaque nouvel essai.

### Puis-je envoyer vers un serveur de mon réseau interne ou sur un autre port ?

Non. Calenoo envoie seulement en HTTPS, sur le port 443, vers une adresse publique.
