> ## Documentation Index
> Fetch the complete documentation index at: https://doc.fluximmo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Suivre les changements de prix en temps réel

> Alerte ADVERT avec events PRICE/REPUBLISHED/UNPUBLISHED, parser le payload, stocker l'historique des prix.

## Goal

Recevoir un webhook **à chaque variation de prix** (et republication / dépublication) sur un périmètre d'annonces, parser le payload, persister un historique `advert_id → (price, occurred_at)` exploitable pour calculer des tendances ou alerter sur les baisses.

## Scénario

Un chasseur immobilier intelligent (ou une fintech / un CRM agence) veut être notifié dès qu'une annonce **déjà découverte** baisse son prix, est republiée, ou disparaît. L'objectif : déclencher une action métier (notif client, re-scoring d'opportunité) **dans la minute** qui suit le changement, sans poller l'API.

## Étapes

<Steps>
  <Step title="1. Pourquoi Adverts (et pas Properties)">
    Seules les **alertes adverts** émettent des events `PRICE`, `REPUBLISHED` et `UNPUBLISHED`. Les alertes properties ne reçoivent que `CREATED` (nouvelle property découverte) et `MERGED` (uniquement quand une nouvelle advert est fusionnée). Si le besoin est *price tracking*, il faut **obligatoirement** raisonner au niveau advert.

    <Snippet file="decision-matrix-property-vs-advert.mdx" />

    Voir [Property vs Advert](/concepts/property-vs-advert) et [Match types & cycle alerte](/concepts/match-types-cycle-alerte) pour la liste exhaustive des events.
  </Step>

  <Step title="2. Créer l'alerte advert avec events">
    Combiner `ALERT_MATCH_CREATED` (première découverte) et `ALERT_MATCH_ADVERT_EVENT` (cycle de vie). Sans `ALERT_MATCH_CREATED`, vous ne recevriez **jamais** d'events car aucune advert n'aurait été matchée historiquement par cette alerte.

    <Snippet file="warning-cycle-vie-alerte.mdx" />

    ```bash theme={null}
    curl -X PUT https://api.fluximmo.io/v2/protected/adverts/search/alerts \
      -H "x-api-key: $FLUXIMMO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "search_query": {
          "filterAd": {
            "location": { "department": "75" },
            "type": ["CLASS_FLAT"],
            "offer": [{ "type": "OFFER_BUY" }],
            "currentPrice": { "value": { "max": 600000 } },
            "isOnline": true
          }
        },
        "webhook_url": "https://votre-domaine.com/webhooks/fluximmo",
        "match": ["ALERT_MATCH_CREATED", "ALERT_MATCH_ADVERT_EVENT"],
        "search_name": "Paris achat - veille prix"
      }'
    ```

    Cf. [Créer une alerte adverts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) (exemple `B2`).
  </Step>

  <Step title="3. Parser le webhook (canonique : data.created + data.updated)">
    Le webhook a la shape canonique `{ data: { created: [{adverts, alert_id}], updated: [{adverts, alert_id}] } }`. Les events `PRICE` / `REPUBLISHED` / `UNPUBLISHED` ne sont **pas émis explicitement** par le serveur — ils se **dérivent côté client** en comparant les adverts de la branche `updated` avec votre état stocké local. Le DTO réduit côté `updated` ne contient que `flxId`, `currentPrice {value, valuePerArea}`, `isOnline`.

    ```text theme={null}
    # Pseudocode handler
    on POST /webhooks/fluximmo:
        body = parse(body)

        # Branche CREATED — AdvertDto complet
        for entry in body.data.created or []:
            for advert in entry.adverts:
                on_new_advert(advert.flxId, advert)
                store(advert.flxId, price = advert.currentPrice.value, online = advert.isOnline)

        # Branche UPDATED — DTO minimal, event dérivé client-side
        for entry in body.data.updated or []:
            for advert in entry.adverts:
                prev = load(advert.flxId)        # état local stocké
                new_price  = advert.currentPrice.value
                new_online = advert.isOnline

                if prev is None:
                    # advert pas encore vue : traiter comme CREATED partiel
                    store(advert.flxId, price = new_price, online = new_online)
                    continue

                if new_price != prev.price:
                    on_price_change(advert.flxId, prev.price, new_price)   # event PRICE dérivé
                if new_online and not prev.online:
                    on_republished(advert.flxId, advert)                   # event REPUBLISHED dérivé
                if not new_online and prev.online:
                    on_unpublished(advert.flxId)                           # event UNPUBLISHED dérivé

                store(advert.flxId, price = new_price, online = new_online)

        return 200
    ```

    <Note>
      Idempotence : dédup côté client avec une clé stable comme `(advert.flxId, alert_id, currentPrice.value, isOnline)`. Vous recevrez parfois le même webhook deux fois sur retry réseau.
    </Note>
  </Step>

  <Step title="4. Stocker l'historique des prix">
    Une table append-only par `(flx_id, occurred_at)` suffit. Chaque event `PRICE` ajoute une ligne.

    ```sql theme={null}
    CREATE TABLE advert_price_history (
      flx_id        TEXT      NOT NULL,
      price         INTEGER   NOT NULL,
      occurred_at   TIMESTAMP NOT NULL DEFAULT NOW(),
      source        TEXT      NOT NULL,  -- 'CREATED' | 'PRICE_DERIVED' | 'REPUBLISHED_DERIVED'
      PRIMARY KEY (flx_id, occurred_at)
    );

    CREATE INDEX idx_price_history_flx ON advert_price_history (flx_id, occurred_at DESC);
    ```

    À chaque webhook (`CREATED` ou `PRICE`), insérer une ligne avec le prix courant. Pour `UNPUBLISHED`, marquer dans une table séparée `advert_lifecycle` plutôt que de polluer l'historique de prix.
  </Step>

  <Step title="5. Calculer une tendance + alerter sur les baisses">
    ```text theme={null}
    # Pseudocode
    trend(flx_id):
        rows = SELECT price, occurred_at FROM advert_price_history
               WHERE flx_id = ? ORDER BY occurred_at
        if rows.length < 2: return null
        first = rows.first.price
        last  = rows.last.price
        return { first, last, delta_pct: round((last - first) / first * 100, 2) }

    on_price_change(flx_id, old_price, new_price):
        if old_price > 0:
            delta_pct = (new_price - old_price) / old_price * 100
            if delta_pct <= -5:           # baisse de 5 % ou plus
                notify_client(flx_id, old_price, new_price, delta_pct)
        insert_price_history(flx_id, new_price, source = "PRICE_DERIVED")
    ```
  </Step>
</Steps>

## Architecture / flow

```mermaid theme={null}
sequenceDiagram
    participant Crawler as Fluximmo Crawler
    participant Engine as Match Engine
    participant Webhook as Votre Worker
    participant DB as advert_price_history
    participant Biz as Logique métier

    Crawler->>Engine: Détecte changement prix sur advert
    Engine->>Engine: Match advert vs alertes existantes
    Engine->>Webhook: POST {data:{created:[],updated:[{adverts:[{flxId,currentPrice}]}]}}
    Webhook->>Webhook: Diff vs état local → event PRICE dérivé
    Webhook->>DB: INSERT (flx_id, price, occurred_at)
    Webhook->>Biz: trigger règle (delta_pct <= -5%)
    Biz->>Biz: Notif client / re-scoring
```

## Pièges fréquents

<Warning>
  **Confondre Properties et Adverts.** Les alertes properties ne reçoivent **pas** d'events `PRICE`. Si vous ne recevez jamais de variation de prix, vérifiez que vous utilisez bien `PUT /v2/protected/adverts/search/alerts` (pas `/properties/search/alerts`).
</Warning>

<Warning>
  **Oublier le diff client-side.** La branche `data.updated[]` ne porte qu'un DTO réduit (`flxId`, `currentPrice`, `isOnline`) — pas de `event_type`, pas de `previous`. Sans diff vs votre état local, vous mélangerez prix, republications et dépublications. Stockez systématiquement (price, isOnline) par flxId pour pouvoir comparer.
</Warning>

<Warning>
  **Recréer l'alerte au lieu de la modifier.** Les events ne s'appliquent qu'aux adverts **historiquement matchées par cette alerte précise**. `DELETE` + `PUT` repart de zéro. Utiliser [`PATCH /alerts/{flxId}`](/api-v2-reference/adverts-alerts/update-the-search-criteria-of-an-advert-alert) pour préserver l'historique.
</Warning>

<Warning>
  **Filtrage trop strict côté UI.** Une variation de **1 €** déclenche un webhook `PRICE`. Si vous affichez chaque event au client final, prévoyez un seuil minimum (ex. `abs(delta_pct) >= 1`) côté UI — pas côté ingestion (gardez tout en BDD).
</Warning>

## Pour aller plus loin

* [Match types & cycle alerte](/concepts/match-types-cycle-alerte) — tous les events ADVERT et PROPERTY détaillés
* [Webhooks](/concepts/webhooks) — format de livraison, retries, sécurité, idempotence
* [Property vs Advert](/concepts/property-vs-advert) — pourquoi Adverts pour le price tracking
* [Créer une alerte adverts](/api-v2-reference/adverts-alerts/create-a-new-advert-alert-for-this-search) — référence complète

<Card title="Clé test gratuite — 1 semaine" icon="key" href="https://my.fluximmo.io">
  Créez un compte sur **my.fluximmo.io** pour récupérer une clé API test gratuite (1 semaine, accès limité). Aucun paiement requis.
</Card>
