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.
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.
booking.created et booking.updated.GET /public/v1/bookings/{id} : dates, canal, statut, montants, champs liés aux missions et vos paramètres personnalisés de réservation.webhooks.write. POST /public/v1/webhooks exige aussi bookings.read, car le payload est un détail de réservation complet.2xx.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.Chaque livraison est un POST HTTP sur votre URL, avec ces en-têtes :
Content-Type: application/jsonUser-Agent: Nowistay-Webhooks/1.0Nowistay-Webhook-Id: 801Nowistay-Webhook-Event-Id: evt_LkP2sxR9zV8QyW... (unique par événement)Nowistay-Webhook-Event-Type: booking.updatedNowistay-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.
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).
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);
}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.
L'objet data.booking reflète ce que renvoie GET /public/v1/bookings/{id} appelé sans le scope bookings.sensitive.read. Ça veut dire :
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"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 :
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.
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.
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.
# 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).
https://user:pass@... est refusé).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.id d'événement peut être redélivré après un échec. Stockez-le et ignorez-le s'il a déjà été traité.2xx immédiatement et traitez l'événement dans une tâche de fond s'il est lourd.Nowistay-Webhook-Event-Id et l'id de requête dans vos logs. Ça accélère le support.GET /public/v1/bookings/{id} après réception.https://api.nowistay.dev et validez votre vérification de signature avant de passer en production.