> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.goodflag.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.goodflag.com/_mcp/server.

# Suivre l'avancement du parapheur

> Réaliser la progression des étapes de signature et de validation d'un parapheur

# Suivi & webhooks

## Deux façons de suivre un parapheur

* **Interrogation (polling)** — relire le parapheur (`GET /api/workflows/{id}`) et lire son
  avancement : `progress`, `steps[].isStarted` / `isFinished`, `currentRecipientEmails` /
  `currentRecipientUsers` (voir la fondation [*Parapheurs*](/wm/guides/integration/creer-un-parapheur)).
* **Webhooks** — la plateforme **pousse** les événements vers un point de terminaison
  (endpoint) de votre application dès qu'ils surviennent. C'est le mécanisme recommandé
  pour une intégration réactive.

*(À ne pas confondre avec les `notifiedEvents` du parapheur, qui déclenchent les notifications par e-mail aux personnes — voir la fondation Parapheurs. Les webhooks, eux, notifient votre application.)*

## Créer un webhook

Deux portées :

* **Webhook utilisateur** — événements des parapheurs **d'un utilisateur** :

```http
POST /api/users/{userId}/webhooks
{ "endpointUrl": "https://votre-app/hook/…", "notifiedEvents": [ "workflowFinished", "recipientFinished" ] }
```

* **Webhook global** — événements de **tous les parapheurs du tenant** :

```http
POST /api/tenants/{tenantId}/webhooks
{ "endpointUrl": "https://votre-app/hook/…", "notifiedEvents": [ … ] }
```

| Champ            | Rôle                                        |
| ---------------- | ------------------------------------------- |
| `endpointUrl`    | URL de votre point de terminaison (requis). |
| `notifiedEvents` | Les événements auxquels s'abonner (requis). |

La réponse renvoie le webhook créé (identifiant `wbh_`).

> **Les en-têtes personnalisés (`customHeaders`) se posent à la mise à jour, pas à la création.**
> La création (`POST`) n'accepte que `endpointUrl` et `notifiedEvents`. Les `customHeaders`
> (en-têtes HTTP ajoutés à chaque appel, notamment le secret d'authentification — section [*Sécuriser et fiabiliser la réception*](#securiser-et-fiabiliser-la-reception)) sont un
> champ de la **mise à jour** (`PATCH`, section [*Cycle de vie d'un webhook*](#cycle-de-vie-dun-webhook)). On crée donc le webhook, puis on pose ses en-têtes :
>
> ```http
> PATCH /api/webhooks/{id}
> If-Match: "<etag>"
> { "customHeaders": { "X-Webhook-Secret": "<secret>" } }
> ```
>
> L'en-tête est ensuite livré **tel quel** à chaque appel.

## Les événements (`eventType` / `notifiedEvents`)

Les types d'événements présents dans la référence :

| Domaine          | `eventType`                                                                                                                          |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Parapheur**    | `workflowStarted`, `workflowStopped`, `workflowFinished`, `workflowFinishedDownloadLink`, `workflowFinishedDownloadLinkNoAttachment` |
| **Destinataire** | `recipientInvite`, `recipientFinished`, `recipientRefused`                                                                           |
| **Commentaire**  | `commentCreated`                                                                                                                     |
| **Cogestion**    | `coManagerAdded`, `coManagerRemoved`                                                                                                 |

Les variantes `workflowFinishedDownloadLink…` correspondent à la fin de parapheur assortie d'un
**lien de téléchargement** des documents signés (option `sendDownloadLink` de l'étape, fondation
*Parapheurs*) ; le suffixe `…NoAttachment` en désigne la variante **sans pièces jointes**. À la
création d'un webhook (section [*Créer un webhook*](#creer-un-webhook)), on s'abonne au sous‑ensemble voulu via `notifiedEvents`.

> Cette liste est **validée par l'API** : un `notifiedEvents` inconnu est **rejeté**. Il n'existe pas encore
> d'événement de clôture/archivage (workflowArchived) mais il est prévu dans la roadmap.
> Pour détecter la fin d'un parapheur, appuyez‑vous sur `workflowFinished`.

## Le message reçu par votre endpoint

Un événement de webhook (identifiant `wbe_`) est **léger** : il indique **ce qui s'est
passé** et **sur quel parapheur**, pas l'état complet. Champs :

| Champ                 | Rôle                                                                                                                                     |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id` (`wbe_`)         | Identifiant de l'événement (clé d'idempotence, section [*Sécuriser et fiabiliser la réception*](#securiser-et-fiabiliser-la-reception)). |
| `eventType`           | Type d'événement (section [*Les événements (eventType / notifiedEvents)*](#les-evenements-eventtype-notifiedevents)).                    |
| `workflowId`          | Parapheur concerné *(le cas échéant)*.                                                                                                   |
| `stepId`              | Étape concernée *(le cas échéant)*.                                                                                                      |
| `webhookId` (`wbh_`)  | Webhook à l'origine de l'appel.                                                                                                          |
| `tenantId` / `userId` | Tenant ; `userId` renseigné pour un **webhook utilisateur**.                                                                             |
| `jobOperation`        | *(le cas échéant)* Opération/tâche en cours à l'origine de l'événement.                                                                  |
| `created` / `updated` | Horodatages de l'événement.                                                                                                              |

> **Règle d'or : relisez toujours l'état via l'API.** À la réception d'un événement, faites un
> `GET /api/workflows/{workflowId}` pour obtenir l'état à jour (avancement,
> destinataires courants, documents…). Ne vous fiez pas au seul contenu de l'événement.

Les événements sont aussi consultables a posteriori : `GET /api/webhookEvents/{id}` et
`GET /api/webhookEvents` (recherche).

## Sécuriser et fiabiliser la réception

* **Rendre l'endpoint joignable** — la plateforme doit pouvoir **atteindre** votre URL depuis
  l'extérieur. Ne placez **pas** de **Basic Auth**, d'**allowlist IP** ni d'autre protection
  d'infrastructure **bloquante** en amont : elle renverrait un `401`/`403` à la plateforme, qui
  considérerait la livraison comme un échec et **relancerait** en vain (voir plus bas). La
  vérification d'authenticité doit se faire **au niveau applicatif**, via le secret d'en-tête
  ci-dessous — et non par une couche d'infrastructure.
* **Authentifier l'appel** — les appels de webhook **ne sont pas signés** (pas de HMAC, aucun
  en‑tête de signature ni de condensat). Authentifiez‑les via un **secret partagé** placé dans
  `customHeaders` (par ex. `X-Webhook-Secret: …`), renvoyé **tel quel** à chaque appel et
  **vérifié** par votre endpoint (comparaison à temps constant). Rappel : `customHeaders` se pose à
  la **mise à jour** du webhook (`PATCH`, section [*Créer un webhook*](#creer-un-webhook)), pas à la création. Servez votre endpoint en
  **HTTPS** avec une URL difficile à deviner.
* **Idempotence & relances** — la livraison est **au moins une fois** : si votre endpoint répond
  autre chose qu'un `2xx` (ou trop lentement), la plateforme **relance** le même événement, à
  **intervalle croissant** — de quelques minutes au début, s'espaçant ensuite (de l'ordre du quart
  d'heure) — et ce **pendant une longue période** (plus d'une heure et de nombreuses tentatives
  observées). Une brève indisponibilité de votre endpoint est ainsi **rattrapée automatiquement** ;
  en contrepartie, un même événement peut vous parvenir **plusieurs fois** : traitez‑le **une seule
  fois** en vous appuyant sur son identifiant `wbe_` (section [*Le message reçu par votre endpoint*](#le-message-recu-par-votre-endpoint)).
* **Répondre vite** — renvoyez un `2xx` rapidement, puis traitez de façon asynchrone ; une
  réponse lente ou en erreur déclenche une relance.

## Cycle de vie d'un webhook

* **Consulter** — `GET /api/webhooks/{id}`.
* **Modifier** — `PATCH /api/webhooks/{id}` (concurrence optimiste `If-Match`, fondation [*Structure > Concurrence : l'en-tête conditionnel `If-Match`*](/wm/api-reference/structure#concurrence-len-tete-conditionnel-if-match)).
* **Supprimer** — `DELETE /api/webhooks/{id}`.
* **Lister** — `GET /api/webhooks` (recherche et filtres, fondation [*Structure > Recherche & pagination*](/wm/api-reference/structure#recherche-pagination)).

## Les journaux : une aide au diagnostic, pas un mécanisme d'intégration

> **Règle, avant tout le reste.** Les journaux du Workflow Manager reflètent son **exécution
> interne**. Ils ne sont **pas destinés à être analysés de façon programmatique**, et rien de ce
> qu'ils contiennent ne constitue un engagement d'interface : le vocabulaire des opérations peut
> changer sans préavis. Pour suivre un parapheur, servez-vous de l'**interrogation** et des
> **webhooks** (section [*Deux façons de suivre un parapheur*](#deux-facons-de-suivre-un-parapheur)) ; pour réagir à une erreur, branchez votre logique sur le champ `code`
> (fondation [*Conventions transverses > Réponses d'erreur*](/wm/api-reference/structure#reponses-derreur)). Cette section décrit les journaux pour les deux
> seuls usages où ils ont leur place : **comprendre ce qui s'est passé pendant le développement**,
> et **fournir un élément au support de Goodflag**.

Cela dit, à ce titre, ils rendent un service qu'aucune autre source ne rend : ils disent ce que la
plateforme **a fait**, horodaté à l'opération, là où l'interrogation ne dit que l'état courant.
C'est décisif face à une question comme *tel message a-t-il été émis ?*, à laquelle une boîte de
réception ne répond pas de façon fiable — un message jamais envoyé et un message mis en quarantaine
par un filtrage entrant y produisent le même constat.

**Le journal de l'étape.** La relecture d'un parapheur (`GET /api/workflows/{id}`) expose un
tableau `logs` sur le parapheur **et sur chacune de ses étapes** — c'est ce dernier qui porte
l'essentiel. Chaque entrée comporte un horodatage `created`, une `operation`, et selon les cas
`recipientEmail`, `recipientUserId` ou `evidenceId`. Vocabulaire **observé** — non documenté par la
référence, donc susceptible d'évoluer — sur un parcours de signature mené à son terme :

| `operation`               | Signification                                                        |
| ------------------------- | -------------------------------------------------------------------- |
| `start`                   | L'étape a démarré.                                                   |
| `notifyWorkflowStarted`   | L'événement « parapheur démarré » a été levé.                        |
| `sign`                    | Un destinataire a signé ; `evidenceId` référence la preuve produite. |
| `notifyRecipientFinished` | L'événement « ce destinataire a terminé » a été levé.                |
| `notifyWorkflowFinished`  | L'événement « parapheur terminé » a été levé.                        |

> **Une entrée `notify…` atteste un événement levé, pas un e-mail expédié.**
> `notifyWorkflowStarted` figure au journal même lorsque `notifiedEvents` est vide et qu'aucun
> message ne part. Ces entrées se lisent donc comme la trace du **déclencheur**, l'envoi effectif
> dépendant ensuite de la configuration des notifications (fondation [*Messages & notifications*](/wm/guides/integration/templates-email)).
> En sens inverse, l'absence de toute entrée relative à un événement signifie que la plateforme ne
> l'a pas levé — donc qu'aucun message n'a pu en découler.

**Le journal d'audit du tenant.** `GET /api/logs` — « Search in all the logs » — restitue les
opérations d'API enregistrées, filtrables notamment par `items.entityId` (l'identifiant d'un
parapheur convient), `items.userId`, `items.operation`, `items.errorCode` et `items.created`.
Chaque entrée porte `operation`, `created`, `duration`, `errorCode` / `errorDetail`, `appVersion`
et un bloc `variables` contenant la requête HTTP reçue. Sur un parapheur créé puis lancé par
l'API, on lit par exemple `createWorkflow`, `createWorkflowParts`, `updateWorkflow`,
`refreshWorkflowViewAuthorizedUsers`, `processWorkflow`, `createWorkflowInvite`, puis
`cleanWorkflowData`. Pagination habituelle, **50 entrées par page au maximum** ; `POST
/api/logs/exports` en produit un export.

Ce sont les entrées `log_…` que désigne le champ `logId` des réponses d'erreur (fondation
[*Conventions transverses > Réponses d'erreur*](/wm/api-reference/structure#reponses-derreur)) : le même journal, consulté par la recherche plutôt que par une
référence isolée. La recommandation y est la même — **transmettre la référence au support plutôt
que de chercher à interpréter le journal soi-même**, car certains incidents ne s'expliquent que
depuis l'intérieur de la plateforme.

> **Portée du jeton.** L'accès à `/api/logs` peut être refusé à un jeton applicatif ordinaire
> (`UserNotAllowed`) : ce journal relève de l'administration du tenant. C'est cohérent avec son
> statut — un outil d'exploitation, pas une dépendance d'intégration. Ne bâtissez pas de
> supervision qui suppose cet accès.