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
- Vous créez une alerte avec des filtres (code postal, type de bien, prix max, etc.) et une URL webhook.
- Chaque jour à 06:00 UTC, le serveur scanne les transactions DVF des 48 dernières heures.
- Pour chaque transaction correspondant à vos filtres, une requête POST est envoyée à votre URL.
- 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
}
}| Champ | Description |
|---|---|
event | Toujours "new_transaction" pour le moment |
alert_id | ID de l'alerte qui a déclenché la notification |
transaction.id | ID unique de la transaction DVF (stable) |
transaction.date_mutation | Date de la vente (YYYY-MM-DD) |
transaction.valeur_fonciere | Prix de vente en euros |
transaction.prix_m2 | Prix au m² calculé par Normi |
transaction.surface_reelle_bati | Surface habitable en m² |
transaction.type_local | Appartement | Maison | etc. |
transaction.commune | Commune en MAJUSCULES (ex. PARIS 11) |
transaction.latitude / longitude | Coordonné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
| Contrainte | Valeur |
|---|---|
| Heure de livraison | Quotidien à 06:00 UTC |
| Fenêtre de détection | Transactions des 48 dernières heures |
| Timeout par requête | 10 secondes |
| Retry sur échec | Aucun — les livraisons échouées ne sont pas réessayées |
| Déduplication | Garantie 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
ngrokoucloudflared tunnelpour exposer votre serveur local pendant le développement.