Skip to content

Webhooks & Polling

L'API Convergence ne fournit pas de webhooks natifs. Opcovia comble ce manque avec un systeme de polling intelligent qui detecte les changements et notifie les clients via des webhooks HMAC-SHA256.

Vue d'ensemble

Poller : detection des changements

Le poller utilise le pipeline existant pour interroger l'OPCO. Il beneficie ainsi du cache, du rate limiting et des transforms.

Flux d'un poll

Le poller utilise dateModification (date du dernier poll) pour ne recuperer que les dossiers modifies depuis la derniere verification.

Flux complet : poll → diff → dispatch

Retry des webhooks en echec

Auto-desactivation

Si les webhooks echouent pendant 7 jours consecutifs, le poller se desactive automatiquement pour eviter de consommer du budget rate limit inutilement.

Auto-scheduler (BullMQ)

Le scheduler utilise une queue BullMQ dediee (opcovia-poller) avec des jobs repeatable :

  • 1 repeatable job par couple (opcoId, tenantHash)
  • Intervalle configurable par OPCO (defaut : 5 minutes)
  • Concurrence limitee a 1 (pas de polls en parallele pour un meme tenant)
  • Priorite basse (10) : les requetes clients passent devant

Enregistrement automatique via sync

Quand un bulk sync se termine avec enablePoller: true, le poller est automatiquement enregistre avec lastPollAt = date de debut du sync. Cela evite de re-scanner les dossiers deja recuperes.

Webhook Dispatcher

Signature HMAC-SHA256

Chaque webhook possede un secret genere a la creation. Les payloads sont signes :

signature = HMAC-SHA256(secret, JSON.stringify(payload))

Headers envoyes :

HeaderValeur
Content-Typeapplication/json
Opcovia-SignatureSignature HMAC hex
Opcovia-EventType d'evenement

Retry

TentativeDelai avant retry
1-
21 seconde
35 secondes

Timeout par delivery : 10 secondes. Apres 3 echecs, la delivery est marquee failed.

Chaque tentative est loguee dans webhook_deliveries (statusCode, attempts, error).

Garantie : at-least-once

Le poller persiste le snapshot (lastPollAt) apres avoir dispatche les webhooks. En cas de crash entre le dispatch et la persistance, le prochain poll re-detectera les memes changements et les re-dispatchera. Le client doit etre idempotent.

Types d'evenements

EvenementDeclencheurPayload.data
dossier.updatedPoller detecte des changements{ dossiers, count }
dossier.etat_changedPoller detecte un changement d'etat{ dossier, oldEtat, newEtat }
facture.etat_changedPoller detecte un changement d'etat facture{ facture, oldEtat, newEtat }
job.completedWorker termine un job avec succes{ jobId, type, result }
job.failedWorker echoue apres 3 retries{ jobId, type, error }
job.progressStep d'un composite route terminee{ jobId, step, totalSteps, result }
sync.startedDiscovery sync terminee, batches lances{ syncId, totalEstimate, annees }
sync.batchUn batch de sync recupere{ syncId, batch, totalBatches, dossiers }
sync.completedSync complete{ syncId, total, durationMs }

CRUD Webhooks

RouteMethodeDescription
/webhooksPOSTCreer un webhook { callbackUrl, events }
/webhooksGETLister les webhooks du tenant
/webhooks/:idPATCHModifier (callbackUrl, events, active)
/webhooks/:idDELETESupprimer
/webhooks/:id/logsGETHistorique des deliveries

Protection SSRF

La callbackUrl est validee contre les attaques SSRF :

  • Protocole HTTPS obligatoire (sauf en dev)
  • Interdiction de localhost, 127.0.0.1, ::1
  • Interdiction des reseaux prives (10.x, 192.168.x, 172.16-31.x)
  • Interdiction des domaines .local, .internal

Bulk Sync

Le bulk sync est un processus lourd qui recupere tous les dossiers d'un CFA. Il est orchestre via BullMQ avec deux types de jobs :

POST /sync/dossiers { opcoId, minYear?, enablePoller? }
  → 202 { syncId }

┌──────────────────────────────────────────────┐
│ sync-discovery (priority: low)               │
│                                              │
│ Pass 1 : page 1 de chaque annee             │
│   → totalEstimate connu rapidement           │
│   → webhook sync.started                     │
│                                              │
│ Pass 2 : pages restantes                     │
│   → tous les IDs collectes                   │
│                                              │
│ Enqueue N sync-batch jobs (50 IDs chacun)    │
└──────────────────────────────────────────────┘

         ▼ (x N jobs en parallele)
┌──────────────────────────────────────────────┐
│ sync-batch                                   │
│                                              │
│ GET /dossiers/liste?numerosInternes=...      │
│ UPDATE fetched = fetched + N (atomique SQL)  │
│ webhook sync.batch { dossiers }              │
│                                              │
│ Si dernier batch :                           │
│   webhook sync.completed                     │
│   Si enablePoller : register auto-scheduler  │
└──────────────────────────────────────────────┘

Progress consultable via GET /sync/:syncId (total, fetched, progress %, batchesCompleted).