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 :
| Header | Valeur |
|---|---|
Content-Type | application/json |
Opcovia-Signature | Signature HMAC hex |
Opcovia-Event | Type d'evenement |
Retry
| Tentative | Delai avant retry |
|---|---|
| 1 | - |
| 2 | 1 seconde |
| 3 | 5 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
| Evenement | Declencheur | Payload.data |
|---|---|---|
dossier.updated | Poller detecte des changements | { dossiers, count } |
dossier.etat_changed | Poller detecte un changement d'etat | { dossier, oldEtat, newEtat } |
facture.etat_changed | Poller detecte un changement d'etat facture | { facture, oldEtat, newEtat } |
job.completed | Worker termine un job avec succes | { jobId, type, result } |
job.failed | Worker echoue apres 3 retries | { jobId, type, error } |
job.progress | Step d'un composite route terminee | { jobId, step, totalSteps, result } |
sync.started | Discovery sync terminee, batches lances | { syncId, totalEstimate, annees } |
sync.batch | Un batch de sync recupere | { syncId, batch, totalBatches, dossiers } |
sync.completed | Sync complete | { syncId, total, durationMs } |
CRUD Webhooks
| Route | Methode | Description |
|---|---|---|
/webhooks | POST | Creer un webhook { callbackUrl, events } |
/webhooks | GET | Lister les webhooks du tenant |
/webhooks/:id | PATCH | Modifier (callbackUrl, events, active) |
/webhooks/:id | DELETE | Supprimer |
/webhooks/:id/logs | GET | Historique 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).