API publique : recevoir les webhooks de réservation en temps réel

Tous les articles d'aide
>
API publique : recevoir les webhooks de réservation en temps réel

Les webhooks sont la façon la plus rapide de rester synchronisé avec Nowistay. Au lieu d'interroger l'API toutes les quelques minutes pour détecter les nouvelles réservations ou les changements, vous indiquez à Nowistay l'URL où envoyer les événements, et votre service les reçoit dès qu'ils se produisent. Cet article vous montre comment créer un webhook, vérifier la signature et gérer les nouvelles tentatives.

Petit rappel : c'est quoi l'API publique

L'API REST publique Nowistay permet à vos outils et partenaires de lire et d'écrire les données Nowistay en HTTPS. Vous vous authentifiez avec OAuth2 (authorization code + PKCE), vous obtenez un token d'accès, puis vous appelez les endpoints sous /public/v1/*. Les webhooks sont la version push : mêmes données, même format que GET /public/v1/bookings/{id}, mais c'est Nowistay qui vous les envoie dès qu'il y a un changement.

Si vous n'avez pas encore configuré OAuth, commencez par le guide principal : Utiliser l'API REST publique Nowistay. Il couvre l'enregistrement, le flux de consentement, les scopes et comment tester depuis le playground des docs.

Ce que vous recevez

  • Livraison en temps réel des événements booking.created et booking.updated.
  • Le même JSON que GET /public/v1/bookings/{id} : dates, canal, statut, montants, champs liés aux missions et vos paramètres personnalisés de réservation.
  • Une signature HMAC-SHA256 pour vérifier que la requête vient bien de Nowistay.
  • Nouvelles tentatives automatiques avec délai croissant en cas d'échec de votre endpoint.
  • Désactivation automatique après 72 heures d'échecs continus (réactivable depuis l'API).

Avant de commencer

  • Il vous faut l'abonnement AI Channel Manager sur la propriété. Sans lui, les livraisons sont ignorées.
  • Le token OAuth utilisé pour créer le webhook doit porter le scope webhooks.write. POST /public/v1/webhooks exige aussi bookings.read, car le payload est un détail de réservation complet.
  • Votre URL de réception doit être accessible publiquement, servie en HTTPS en production, et répondre en moins de 10 secondes avec un statut 2xx.
  • Un webhook est lié à une seule propriété. Vous pouvez en enregistrer jusqu'à 3 par propriété.

Étape 1. Créez un webhook

curl -X POST https://api.nowistay.com/public/v1/webhooks \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{
    "propertyId": 101,
    "url": "https://partenaire.exemple.com/nowistay/webhooks",
    "events": ["booking.created", "booking.updated"],
    "description": "Sync PMS"
  }'

Réponse :

{
  "id": 801,
  "propertyId": 101,
  "url": "https://partenaire.exemple.com/nowistay/webhooks",
  "events": ["booking.created", "booking.updated"],
  "description": "Sync PMS",
  "status": "active",
  "createdAt": "2026-05-27T16:40:00Z",
  "updatedAt": "2026-05-27T16:40:00Z",
  "signingSecret": "whsec_9npwH6J8QyZp1L8U3xqG7sN2"
}
Sauvegardez tout de suite le signingSecret. Il n'est renvoyé qu'une seule fois, à la création. Il est impossible de le récupérer plus tard depuis l'API. Si vous le perdez, supprimez le webhook et recréez-en un.

Étape 2. Recevez votre premier événement

Chaque livraison est un POST HTTP sur votre URL, avec ces en-têtes :

  • Content-Type: application/json
  • User-Agent: Nowistay-Webhooks/1.0
  • Nowistay-Webhook-Id: 801
  • Nowistay-Webhook-Event-Id: evt_LkP2sxR9zV8QyW... (unique par événement)
  • Nowistay-Webhook-Event-Type: booking.updated
  • Nowistay-Webhook-Timestamp: 1779292800 (secondes Unix)
  • Nowistay-Webhook-Signature: v1=3a7b...e1c (HMAC-SHA256 hex)

Et un corps JSON dans ce format :

{
  "id": "evt_LkP2sxR9zV8QyW...",
  "type": "booking.updated",
  "createdAt": "2026-05-27T16:40:00Z",
  "apiVersion": "2026-05-27",
  "propertyId": 101,
  "bookingId": 9001,
  "data": {
    "booking": {
      "id": 9001,
      "propertyId": 101,
      "arrival": "2026-07-14",
      "departure": "2026-07-18",
      "status": "confirmed",
      "channel": "airbnb",
      "reservationNumber": "HMABCDE",
      "guest": { "firstName": "Maya", "lastName": "M." },
      "amounts": { "amount": "620.00", "currency": "EUR" },
      "identity": { "status": "verified" }
    }
  }
}

Votre endpoint doit répondre avec un statut 2xx (200 ou 204 typiquement). Tout autre statut, ou une réponse qui prend plus de 10 secondes, compte comme un échec et déclenche une nouvelle tentative.

Étape 3. Vérifiez la signature

Vérifiez toujours la signature avant de faire confiance au contenu. Sans cette vérification, n'importe qui qui devine votre URL pourrait envoyer de faux événements à votre service.

Nowistay calcule la signature ainsi :

signature = "v1=" + HMAC_SHA256(
  signing_secret,
  timestamp + "." + event_id + "." + raw_request_body
).hex()

Recalculez-la côté serveur en utilisant les en-têtes Nowistay-Webhook-Timestamp, Nowistay-Webhook-Event-Id et le corps brut exact de la requête (ne parsez pas et ne sérialisez pas le JSON avant la vérification, vous risqueriez une différence d'octets).

Exemple Node.js

import crypto from "node:crypto";

function verify(req, signingSecret) {
  const timestamp = req.headers["nowistay-webhook-timestamp"];
  const eventId = req.headers["nowistay-webhook-event-id"];
  const signature = req.headers["nowistay-webhook-signature"];
  const rawBody = req.rawBody; // Buffer du corps HTTP brut

  const signed = Buffer.concat([
    Buffer.from(`${timestamp}.${eventId}.`, "utf8"),
    rawBody,
  ]);
  const expected = "v1=" + crypto
    .createHmac("sha256", signingSecret)
    .update(signed)
    .digest("hex");

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Exemple Python

import hmac, hashlib

def verify(request, signing_secret: str) -> bool:
    timestamp = request.headers["Nowistay-Webhook-Timestamp"]
    event_id = request.headers["Nowistay-Webhook-Event-Id"]
    signature = request.headers["Nowistay-Webhook-Signature"]
    raw_body = request.body  # bytes

    signed = f"{timestamp}.{event_id}.".encode("utf-8") + raw_body
    expected = "v1=" + hmac.new(
        signing_secret.encode("utf-8"),
        signed,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

Vérifiez aussi que le timestamp est récent (par exemple, dans les 5 dernières minutes) pour vous protéger des attaques par rejeu.

Ce qui est (et n'est pas) dans le payload

L'objet data.booking reflète ce que renvoie GET /public/v1/bookings/{id} appelé sans le scope bookings.sensitive.read. Ça veut dire :

  • Inclus : dates, canal, statut, montants, prénom et nom du voyageur, adultes/enfants, paramètres personnalisés de réservation (si activés sur le webhook).
  • Non inclus : email du voyageur, téléphone, adresse, ville, code postal, pays, langue, et code de serrure. Ces champs sont considérés comme sensibles et Nowistay ne les diffuse pas aux abonnés webhook.

Quand votre outil a besoin de l'un de ces champs sensibles, allez chercher le détail de la réservation via l'API avec un token qui porte le scope bookings.sensitive.read. Le bookingId contenu dans l'enveloppe du webhook est la bonne entrée pour cet appel.

curl https://api.nowistay.com/public/v1/bookings/9001 \
  -H "Authorization: Bearer nis_access_token_avec_scope_sensitive"

Nouvelles tentatives et désactivation automatique à 72 heures

Si votre endpoint renvoie un statut autre que 2xx, dépasse le délai, ou échoue pour une raison quelconque, Nowistay ne perd pas l'événement. Une nouvelle tentative est planifiée, avec un délai qui double à chaque échec et un peu de jitter :

  • 1ʳᵉ nouvelle tentative : environ 1 minute après l'échec.
  • 2ᵉ : environ 2 minutes plus tard.
  • 3ᵉ : environ 4 minutes, puis 8, 16, 32 et ainsi de suite.
  • Plafond : au maximum 6 heures entre deux tentatives.

Cela continue jusqu'à ce que votre endpoint accepte l'événement, ou jusqu'à ce que le webhook ait échoué pendant plus de 72 heures d'affilée. À ce moment-là Nowistay met le webhook en statut disabled et arrête de lui envoyer de nouveaux événements. Les compteurs d'échec se réinitialisent dès qu'une livraison réussit, donc un incident isolé ne déclenche jamais la désactivation.

Les enregistrements de livraison sont conservés pendant 30 jours, puis nettoyés.

Réactiver un webhook désactivé

Un webhook désactivé peut être réactivé depuis l'API une fois que votre endpoint est de nouveau en bonne santé. Envoyez un PATCH avec status: "active" :

curl -X PATCH https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{"status": "active"}'

Nowistay ne rejoue pas les événements qui se sont produits pendant la désactivation. Si vous en avez manqué, faites un rattrapage ponctuel avec GET /public/v1/bookings et un filtre de dates, puis reprenez votre flux webhook normal.

Mettre en pause, reprendre, supprimer

Vous pouvez suspendre temporairement les livraisons (pendant une fenêtre de maintenance par exemple) sans perdre la configuration du webhook :

# Mettre en pause
curl -X PATCH https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}'

# Reprendre
curl -X PATCH https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{"status": "active"}'

# Supprimer definitivement
curl -X DELETE https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example"

Vous pouvez aussi modifier url, la liste events ou la description dans le même appel. Changer l'URL ne change pas la clé de signature.

Inspecter vos webhooks

# Lister tous les webhooks (filtre par propriete optionnel)
curl "https://api.nowistay.com/public/v1/webhooks?propertyId=101" \
  -H "Authorization: Bearer nis_access_token_example"

# Lire un webhook
curl https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example"

La réponse liste affiche les mêmes champs que la réponse de création, sans la clé de signature (elle n'est renvoyée qu'à la création).

Règles d'URL et limites

  • URL absolue HTTP ou HTTPS, 2048 caractères maximum. HTTPS obligatoire en production.
  • Pas de credentials dans l'URL (https://user:pass@... est refusé).
  • Pas de localhost, pas de plages d'IP privées ou réservées. Nowistay résout aussi le hostname au moment de la livraison et bloque les appels qui résolvent vers une adresse privée.
  • Pas de redirections HTTP. Nowistay ne les suit pas, la livraison est marquée en échec si votre serveur redirige.
  • Maximum 3 webhooks par propriété, toutes apps confondues.
  • Description : optionnelle, 120 caractères maximum.

Bonnes pratiques

  • Vérifiez la signature à chaque appel, y compris en dev. C'est la seule chose qui prouve que la requête vient bien de Nowistay.
  • Soyez idempotent. Le même id d'événement peut être redélivré après un échec. Stockez-le et ignorez-le s'il a déjà été traité.
  • Répondez vite. Accusez réception avec un 2xx immédiatement et traitez l'événement dans une tâche de fond s'il est lourd.
  • Loggez les en-têtes. Gardez Nowistay-Webhook-Event-Id et l'id de requête dans vos logs. Ça accélère le support.
  • Traitez le webhook comme un signal, pas comme la source de vérité. Si votre logique métier a besoin de champs sensibles ou de la donnée la plus à jour, appelez GET /public/v1/bookings/{id} après réception.
  • Testez d'abord en préproduction. Pointez un endpoint de staging sur https://api.nowistay.dev et validez votre vérification de signature avant de passer en production.

Pour aller plus loin

Prêt à Mettre Votre Airbnb en Pilote Automatique ?

Rejoignez 300+ professionnels qui automatisent les échanges avec les voyageurs et gagnent un temps précieux.