Guides / Webhooks

Intégrer les webhooks

Les alertes webhook notifient votre serveur dès qu'une nouvelle transaction DVF correspond à vos critères. Fonctionnalité réservée aux plans Pro et Enterprise.

Plan Pro ou Enterprise requis
Les alertes webhook ne sont pas disponibles sur les plans Gratuit, Indie et Agent. Voir les tarifs.

Comment ça fonctionne

  1. Vous créez une alerte avec des filtres (code postal, type de bien, prix max, etc.) et une URL webhook.
  2. Chaque jour à 06:00 UTC, le serveur scanne les transactions DVF des 48 dernières heures.
  3. Pour chaque transaction correspondant à vos filtres, une requête POST est envoyée à votre URL.
  4. Les transactions déjà notifiées ne sont jamais renvoyées (déduplication côté serveur).

Créer une alerte

Via l'API REST ou les outils MCP alert_create :

curl -X POST "https://mcp.normi.fr/v1/alerts" \
  -H "X-API-Key: normi_votre_token" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Apparts 75011 < 600k",
    "webhook_url": "https://votre-serveur.com/webhooks/normi",
    "filters": {
      "code_postal": "75011",
      "type_local": "Appartement",
      "prix_max": 600000
    }
  }'

Maximum 20 alertes actives par token API. Documentation complète /v1/alerts.

Format du payload

Votre endpoint reçoit un POST JSON avec le corps suivant :

{
  "event": "new_transaction",
  "alert_id": "alert_x9y8z7",
  "transaction": {
    "id": "dvf_3847291",
    "date_mutation": "2026-04-17",
    "valeur_fonciere": 545000,
    "prix_m2": 9316,
    "surface_reelle_bati": 58.5,
    "type_local": "Appartement",
    "nombre_pieces_principales": 3,
    "commune": "PARIS 11",
    "code_postal": "75011",
    "latitude": 48.8596,
    "longitude": 2.3712
  }
}
ChampDescription
eventToujours "new_transaction" pour le moment
alert_idID de l'alerte qui a déclenché la notification
transaction.idID unique de la transaction DVF (stable)
transaction.date_mutationDate de la vente (YYYY-MM-DD)
transaction.valeur_foncierePrix de vente en euros
transaction.prix_m2Prix au m² calculé par Normi
transaction.surface_reelle_batiSurface habitable en m²
transaction.type_localAppartement | Maison | etc.
transaction.communeCommune en MAJUSCULES (ex. PARIS 11)
transaction.latitude / longitudeCoordonnées GPS (peut être null si non géocodé)

Implémenter votre endpoint

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/webhooks/normi", methods=["POST"])
def handle_normi_webhook():
    data = request.json

    # Vérifier l'event type
    if data.get("event") != "new_transaction":
        return jsonify({"ok": True})

    tx = data["transaction"]
    alert_id = data["alert_id"]
    tx_id = tx["id"]

    # Idempotence côté consommateur : ignorer les doublons
    if already_processed(alert_id, tx_id):
        return jsonify({"ok": True})

    # Traitement de la transaction
    print(f"Nouvelle transaction: {tx['valeur_fonciere']:,} € à {tx['commune']}")
    save_to_db(alert_id, tx_id, tx)

    return jsonify({"ok": True})

SLA et contraintes de livraison

ContrainteValeur
Heure de livraisonQuotidien à 06:00 UTC
Fenêtre de détectionTransactions des 48 dernières heures
Timeout par requête10 secondes
Retry sur échecAucun — les livraisons échouées ne sont pas réessayées
DéduplicationGarantie côté serveur (UNIQUE index alert_id × transaction_id)
Délai max avant notification~30h (transaction DVF + livraison J+1 à 06:00 UTC)
Pas de retry automatique
Si votre endpoint est inaccessible au moment de la livraison, la notification est perdue. Assurez-vous que votre serveur webhook est disponible 24h/24. Vous pouvez compenser en appelant manuellement GET /v1/transactionsavec les mêmes filtres pour récupérer les transactions manquées.

Idempotence côté consommateur

Normi garantit la déduplication côté serveur (une même transaction n'est jamais livrée deux fois pour la même alerte). Cependant, en cas d'erreur réseau ou de redémarrage de votre serveur, votre endpoint peut recevoir un doublon. Implémentez une vérification basée sur transaction.id dans votre stockage.

-- Exemple de table idempotente
CREATE TABLE received_webhook_events (
  alert_id   TEXT NOT NULL,
  tx_id      TEXT NOT NULL,
  received_at TIMESTAMP DEFAULT NOW(),
  PRIMARY KEY (alert_id, tx_id)
);

Bonnes pratiques

  • Répondez vite : retournez HTTP 200 immédiatement, puis traitez en arrière-plan. Le timeout est de 10 secondes — si vous dépassez, la livraison est marquée échouée.
  • HTTPS uniquement : votre URL webhook doit être en HTTPS. Les URLs HTTP sont rejetées.
  • Validez le payload : vérifiez que event === "new_transaction" avant de traiter. De nouveaux types d'événements pourraient être ajoutés à l'avenir.
  • Loggez les réceptions : conservez le payload brut pour faciliter le débogage en cas de données inattendues.
  • Testez avec un tunnel : utilisez ngrok ou cloudflared tunnel pour exposer votre serveur local pendant le développement.