> ## 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.

# Bonnes pratiques

> Pagination, retry, idempotence, refetch, validation webhook x-webhook-key — checklist production-ready.

Cette page rassemble les patterns que nous voyons systématiquement chez les intégrations qui tournent en production sans alerte.

## Pagination — `searchAfterHash`

Fluximmo utilise une pagination **par curseur**, pas par offset. Chaque réponse inclut un `searchAfterHash` à renvoyer dans la requête suivante pour récupérer la page d'après.

<Warning>
  Il n'existe **pas** de paramètre `offset` ou `page`. Toute tentative de "skipper" des pages doit passer par le curseur.
</Warning>

```python Python theme={null}
def paginate(client, payload, page_size=100):
    payload["size"] = page_size
    cursor = None

    while True:
        if cursor:
            payload["searchAfterHash"] = cursor

        resp = client.post("/v2/protected/properties/search", json=payload).json()
        data = resp.get("data", {})
        items = data.get("items", [])
        if not items:
            break

        yield from items

        cursor = data.get("searchAfterHash")
        if not cursor:
            break
```

## Retry côté client (request)

| Code HTTP        | Retry ?              | Stratégie                                              |
| ---------------- | -------------------- | ------------------------------------------------------ |
| `2xx`            | Non                  | OK                                                     |
| `4xx` (sauf 429) | **Non**              | Erreur côté appelant — corriger le payload             |
| `429`            | Oui                  | Respecter `Retry-After` si présent, sinon backoff      |
| `5xx`            | Oui (3-5 tentatives) | **Backoff exponentiel + jitter** : `2^n + random(0,1)` |

<Tip>
  Le **jitter** (composante aléatoire) évite le "thundering herd" si plusieurs workers retentent en même temps après un incident.
</Tip>

## Idempotence côté worker

Tous les flux Fluximmo (search, alertes, webhooks) peuvent livrer le même `flxId` plusieurs fois. Conséquence : votre persistance doit être idempotente.

```sql PostgreSQL — UPSERT par flxId theme={null}
INSERT INTO properties (flx_id, payload, updated_at)
VALUES ($1, $2, NOW())
ON CONFLICT (flx_id)
DO UPDATE SET payload = EXCLUDED.payload, updated_at = EXCLUDED.updated_at;
```

## Refetch après webhook PROPERTIES

Les webhooks Properties **ne livrent que les `flxId`** (pas le payload complet). Vous devez refetcher la donnée pour la persister.

<CodeGroup>
  ```python Refetch unitaire theme={null}
  flx_id = webhook_payload["flxId"]
  prop = client.get(f"/v2/protected/properties/{flx_id}").json()["data"]
  upsert(prop)
  ```

  ```python Refetch bulk (recommandé) theme={null}
  # Si votre webhook livre N IDs, batcher l'appel bulk plutôt que N appels unitaires
  ids = [w["flxId"] for w in webhook_batch]
  props = client.post("/v2/protected/properties/_many", json={"flxIds": ids}).json()
  for prop in props.get("data", []):
      upsert(prop)
  ```
</CodeGroup>

<Note>
  Les webhooks **Adverts**, eux, contiennent le payload complet. Pas de refetch nécessaire.
</Note>

## Validation `x-webhook-key`

Toujours valider le header avec une comparaison **timing-safe** (voir [Livraison des webhooks](/ressources/webhooks-livraison#validation-du-header-x-webhook-key)).

<CodeGroup>
  ```python Python theme={null}
  import secrets
  if not secrets.compare_digest(received_key, expected_key):
      abort(401)
  ```

  ```javascript Node.js theme={null}
  import crypto from "node:crypto";
  if (!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
    return res.sendStatus(401);
  }
  ```
</CodeGroup>

## Filtre par défaut : exclure les biens offline

Activer un filtre offline est en général ce que vous voulez en production, sauf cas analytique rétrospectif.

* **Côté PROPERTIES** : `filterProperty.meta.isTotallyOffline: false` exclut les biens dont **toutes** les annonces sont actuellement offline.
* **Côté ADVERTS (alerte)** : `filterAd.isOnline: true` ne retient que les annonces actuellement publiées sur leur portail.

```json Payload search PROPERTIES theme={null}
{
  "size": 25,
  "sortBy": "FIRST_SEEN_AT",
  "orderBy": "DESC",
  "search": {
    "filterProperty": {
      "meta": {
        "isTotallyOffline": false,
        "firstSeenAt": { "min": "2025-01-01T00:00:00.000Z" }
      }
    }
  }
}
```

## Tri par défaut : `FIRST_SEEN_AT DESC`

`sortBy` au root du payload accepte : `FIRST_SEEN_AT`, `PRICE`, `LAST_UPDATED_AT`, `LAST_SEEN_AT`, `RELEVANCE`. Combiné avec `orderBy: "DESC"`, le tri canonique en production est `FIRST_SEEN_AT DESC` — vous récupérez d'abord les biens les plus récemment ingérés. Ne changez pas `sortBy` / `orderBy` entre deux pages : le cursor de pagination devient invalide.

## Couvrir passé + futur

Pour ne rater aucun bien correspondant à un critère :

1. **Côté PROPERTIES** — `POST /v2/protected/properties/search` one-shot pour le passé + alerte property pour le flux continu.
2. **Côté ADVERTS** — alerte advert pour le flux continu + demande de **backfill historique sur webhook** par mail à [contact@fluximmo.com](mailto:contact@fluximmo.com) (volume + dates précisés).
3. **Dédupliquez** côté client avec `flxId` comme clé d'idempotence.

## Checklist production-ready

* [ ] Clé API stockée en variable d'environnement (jamais en dur)
* [ ] Header `x-api-key` envoyé sur toutes les requêtes
* [ ] Retry avec backoff exponentiel + jitter sur 5xx et 429
* [ ] Pas de retry sur 4xx (sauf 429)
* [ ] UPSERT par `flxId` côté persistance
* [ ] Endpoint webhook valide `x-webhook-key` en timing-safe
* [ ] Endpoint webhook acquitte en \< 1s (queue + worker async)
* [ ] Refetch via `/properties/{flxId}` ou bulk après webhook Properties
* [ ] Filtre offline activé par défaut (`meta.isTotallyOffline=false` côté PROPERTIES, `isOnline=true` côté ADVERTS)
* [ ] Tri canonique `sortBy: "FIRST_SEEN_AT", orderBy: "DESC"` au root du payload search
* [ ] Filtre temporel `meta.firstSeenAt.min` aligné sur votre besoin (typiquement `2025-01-01T00:00:00.000Z` minimum)
* [ ] Monitoring : taux d'erreur 5xx, latence webhook ack, taille de queue
