Ajouter un point de terminaison

Connectez-vous, ouvrez Intégrations à partir de votre compte, puis ajoutez l’URL de votre point de terminaison sous Webhooks. Elle doit commencer par https. Choisissez les événements qu’il reçoit, ou tous.

Nolan affiche alors une seule fois le secret de signature du point de terminaison. Rangez-le avec vos autres secrets : il sert à vérifier qu’une requête vient bien de Nolan. « Nouveau secret » le remplace, et l’ancien cesse aussitôt de fonctionner.

« Envoyer un événement test » livre tout de suite un événement ping à ce point de terminaison, pour vérifier votre récepteur avant l’arrivée des vrais événements.

Ce que Nolan envoie

Une requête POST avec un corps JSON et ces en-têtes :

Content-Typeapplication/json
LeaseOwl-Event-IdL’identifiant de l’événement, le même à chaque nouvelle tentative. Servez-vous-en pour ignorer un événement déjà traité.
LeaseOwl-Event-TypeLe type d’événement, par exemple lead.created.
LeaseOwl-Delivery-IdCette livraison de l’événement à ce point de terminaison.
LeaseOwl-TimestampLe moment de cette tentative, en secondes Unix.
LeaseOwl-Signaturet=<horodatage>,v1=<signature>. Voir plus bas.

Vérifier la signature

La signature est un HMAC-SHA256, en hexadécimal minuscule, de l’horodatage, d’un point et du corps brut de la requête, avec le secret de signature du point de terminaison comme clé. Calculez-la sur le corps tel que reçu, avant toute lecture du JSON.

Refusez la requête si aucune valeur v1 ne correspond, ou si l’horodatage s’écarte de plus de cinq minutes de votre horloge. Comparez les signatures en temps constant. Pendant le remplacement d’un secret, vérifiez avec les deux.

Node.js

import crypto from 'node:crypto';

// rawBody: the request body exactly as received, as a string.
export function isFromLeaseOwl(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((part) => part.trim().split('=', 2)));
  const timestamp = Number(parts.t);
  if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}

Nouvelles tentatives et échecs

  • Une livraison réussit quand votre point de terminaison répond avec un statut 2xx en moins de 10 secondes. Répondez d’abord, faites le travail ensuite.
  • Toute autre réponse est relivrée après environ 1 minute, 5 minutes, 30 minutes, 2 heures, 6 heures, 12 heures et 24 heures : huit tentatives sur environ deux jours. Les relivraisons partent toutes les 15 minutes, alors les courts délais peuvent s’allonger.
  • La livraison se fait au moins une fois. Un même événement peut arriver deux fois, et les événements peuvent arriver dans le désordre : utilisez l’identifiant de l’événement pour écarter les doublons, et les dates dans les données pour les ordonner.
  • Les redirections ne sont pas suivies. Utilisez l’URL finale.
  • Après la dernière tentative, la livraison est marquée en échec. Les livraisons en échec sont listées sous Webhooks dans votre page Intégrations, avec un bouton Réessayer.
  • Un point de terminaison qui répond 410 Gone est désactivé. C’est ainsi que Zapier et Make se désabonnent.

L’événement

Chaque événement a la même enveloppe. data contient les objets propres à son type.

{
  "id": "evt_Qm3c9rT2xV8kLw1sZp4bNg",
  "type": "showing.booked",
  "created_at": "2026-10-07T23:47:12.311Z",
  "api_version": "2026-10-05",
  "organization_id": "4c0d6f3e-2b8a-4f61-9a57-0f3c1d2e8b90",
  "data": {
    "showing": {
      "id": "b7c1f0a2-5d3e-4c8b-8e21-6a9f0d4c3b12",
      "status": "booked",
      "starts_at": "2026-10-09T22:00:00.000Z",
      "ends_at": null,
      "when": "Thursday, October 9 at 6 p.m.",
      "previous_starts_at": null,
      "address": "Rue Saint-Denis near Rue Rachel",
      "listing": { "id": "7f3a1c2d-...", "address": "Rue Saint-Denis near Rue Rachel", "rent": 1950 }
    },
    "lead": {
      "id": "b1e4a9d0-...",
      "name": "Maya Tremblay",
      "phone": "+15145550142",
      "email": "",
      "status": "showing_booked",
      "lost_reason": null,
      "channel": "phone",
      "source": "Kijiji",
      "source_line": "+15145550100",
      "source_page": null,
      "listing": { "id": "7f3a1c2d-...", "address": "Rue Saint-Denis near Rue Rachel", "rent": 1950 },
      "wants": { "move_in": "November 1", "household": "2 adults", "pets": "1 cat", "unit": "Rue Saint-Denis near Rue Rachel" },
      "summary": "Wants the Plateau 4½ for November 1, works nearby, has one cat.",
      "callback_requested": false,
      "owner": null,
      "booking_id": "b7c1f0a2-5d3e-4c8b-8e21-6a9f0d4c3b12",
      "showing_at": "2026-10-09T22:00:00.000Z",
      "showing_outcome": null,
      "created_at": "2026-10-07T23:41:55.120Z",
      "last_contact_at": "2026-10-07T23:47:10.004Z"
    }
  }
}

Types d’événements

lead.createdUn locataire vous joint pour la première fois, ou un prospect est ajouté par importation, par l’API ou par Zapier. data : lead.
lead.updatedUn élément du prospect a changé : coordonnées, responsable, résumé, statut ou visite, que Nolan ou votre équipe l’ait changé. data : lead, changed (les champs modifiés, nommés comme dans l’objet lead : name, wants.move_in, owner...).
lead.status_changedUn prospect a changé d’étape : new, contacted, qualified, showing_booked, showed, applied, leased ou lost. Aussi envoyé comme lead.updated. data : lead, previous_status, status, detail.
conversation.messageUn message a été reçu ou envoyé dans un fil de textos, de clavardage ou de courriels. Un appel arrive en un seul message avec son résumé. Un brouillon de Nolan part une fois que votre équipe l’approuve. data : conversation, message (direction inbound ou outbound, author renter, charlie ou team), lead.
showing.bookedUne visite a été réservée, ou déplacée (showing.status vaut rescheduled). data : showing, lead.
showing.cancelledUne visite a été annulée. data : showing, lead.
showing.showedLe locataire s’est présenté à la visite. data : showing, lead.

L’objet lead

idL’identifiant Nolan du prospect.
name, phone, emailTels que le locataire les a donnés. Les numéros sont au format E.164 (+15145550142).
status, lost_reasonnew, contacted, qualified, showing_booked, showed, applied, leased ou lost, et la raison donnée par l’équipe quand un prospect est perdu (sinon null).
channelComment le locataire vous a joint la première fois : phone, sms, chat, email, messenger ou instagram.
source, source_line, source_pageD’où vient le prospect, si on le sait : le site d’annonces (ou une importation CSV, Zapier), le numéro Nolan appelé ou texté, et la page où le clavardage a été ouvert. Chacun vaut null si on ne le sait pas.
listingLe logement demandé (id, address, rent), ou null.
wantsCe que le locataire cherche : move_in, household, pets, et unit (l’adresse du logement voulu, une fois connue).
summaryLe résumé de Nolan des conversations jusqu’ici.
ownerLe membre de l’équipe responsable du prospect (id, email, name), ou null.
callback_requestedtrue quand le locataire a demandé que l’équipe le rappelle.
booking_id, showing_at, showing_outcomeLa visite en cours : son identifiant, son début (ISO 8601), et showed ou no_show une fois que l’équipe l’indique. null s’il n’y en a pas.
created_at, last_contact_atLa création du prospect, et le dernier appel ou message du locataire (ISO 8601).

API REST

Zapier, Make et vos propres scripts utilisent la même API, à https://getnolan.ai/api/v1/. Créez une clé sous « Clés d’API » dans votre page Intégrations. Elle n’est affichée qu’une fois.

Envoyez la clé dans l’en-tête Authorization comme jeton Bearer, jamais dans une URL. Une clé donne accès à une seule organisation : tout le reste répond 404.

GET/api/v1/meL’organisation à laquelle appartient la clé. Pratique pour tester une clé.
GET/api/v1/events?type=lead.created&limit=50Les événements récents, du plus récent au plus ancien, gardés 30 jours. Pour interroger l’API au lieu de recevoir des webhooks.
POST/api/v1/hooksAbonner une URL à un type d’événement (REST Hooks). Corps : url, event. La réponse donne l’identifiant et le secret de signature.
DELETE/api/v1/hooks/{id}Se désabonner.
GET/api/v1/leads?status=new&channel=sms&limit=50Les prospects, du dernier contact au plus ancien. Les prospects fusionnés ou supprimés sont exclus.
POST/api/v1/leadsAjouter un prospect. Il faut un numéro de téléphone ou un courriel. Un prospect au même numéro ou au même courriel est complété plutôt que dédoublé : seulement les champs vides, jamais ce que Nolan ou votre équipe a noté.
GET/api/v1/listingsVos logements, les actifs d’abord.

Ajouter un prospect

curl https://getnolan.ai/api/v1/leads \
  -H "Authorization: Bearer $LEASEOWL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Maya Tremblay", "phone": "514-555-0142", "source": "Kijiji", "summary": "Asked about the Plateau 4½"}'

Les erreurs répondent avec un statut et un corps JSON : { "error": { "code": "...", "message": "..." } }.

Versions

Chaque événement porte api_version. De nouveaux champs et de nouveaux types d’événements peuvent apparaître en tout temps : ignorez ce que vous ne connaissez pas. Un changement qui retire ou renomme un champ reçoit une nouvelle version, annoncée d’abord sur cette page.

Ouvrir vos intégrations