Signature électronique (Yousign)
Vue d'ensemble
La plateforme utilise Yousign API v3 pour la signature électronique des documents d'onboarding et de KYB. L'orchestration est server-side (module auth), avec le module esign-core comme couche d'abstraction du provider.
Documents concernés (onboarding — module auth) :
- Contrat-cadre onboarding (
framework_contract) - CGU (
cgu_acceptance) - Mandat SEPA (
sepa_mandate) - Pièces justificatives de délégation — conditionnel quand le signataire n'est pas sur le KBIS (
delegation_proof)
Documents concernés (facturation — module billing, Phase 5) :
- Mandat d'autofacturation distributeur — voir « Mandat d'autofacturation » plus bas et
2.modules/3.facturation.md§ « Autofacturation ».
La gouvernance des templates de documents (markdown versionné, variables JSON, CSS PDF, preview) est gérée dans le back-office admin sous /admin/documents-templates. Le mandat d'autofacturation, en revanche, est rendu par un helper dédié billing/application/self-billing-mandate-document.ts (pdf-lib direct), pas un template versionné admin — le contenu est stable et cadré par le CGI art. 289.
Architecture interne
Le module esign-core (app/mypromo/server/modules/esign-core/) expose :
domain/ports.ts— interfaceESignProvider(port)domain/types.ts— typesESignStatus,ESignSignatureLevel,ESignAuthenticationModeapplication/map-provider-status.ts— mapping statuts Yousign → statuts internesapplication/orchestrate-signature.ts— orchestration création/activation
L'adaptateur Yousign est instancié dans app/mypromo/server/modules/auth/application/signature-orchestrator.ts.
Les enregistrements de signatures sont persistés dans la table auth_signature_requests (modèle Prisma AuthSignatureRequest). Les événements webhook sont stockés dans auth_signature_webhook_events (modèle Prisma AuthSignatureWebhookEvent).
Flux de création d'une signature request
1. Préparation
- Les données signataire sont lues depuis
organization_billing_profilesvia le read model d'onboarding canonique. - Le backend génère le PDF de chaque document depuis son template markdown rendu via le catalogue
DocumentTemplate. - Un dossier interne est créé en statut
pending_creation.
2. Appels API Yousign
POST /signature_requests ← création (name, external_id, delivery_mode)
POST /signature_requests/{id}/documents ← upload PDF (parse_anchors=true)
POST /signature_requests/{id}/signers ← ajout du signataire
POST /signature_requests/{id}/activate ← activation
Contraintes applicatives :
info.localeest envoyé explicitement (fr).- Pour
otp_sms, le numéro est normalisé en E.164 (fallback0X…→+33X…). - Au moins un champ de signature par signataire est fourni avec position explicite sur la page 1.
- Upload en
multipart/form-dataavecparse_anchors=true; en cas d'erreur400, retry enparse_anchors=falsepuis en JSON base64.
3. Mode de livraison
- Onboarding industriel et distributeur :
delivery_mode=email— Yousign envoie directement l'invitation de signature au signataire. - En
delivery_mode=none(pour d'autres flux) : lesignature_linkest récupéré pour diffusion in-app.
Niveaux de vérification d'identité
Une politique interne identityVerificationPolicy contrôle le niveau Yousign par dossier :
| Politique | Niveau Yousign | Mode auth |
|---|---|---|
none | electronic_signature | no_otp, otp_email ou otp_sms |
required | advanced_electronic_signature | otp_sms (obligatoire AES) |
qualified | qualified_electronic_signature | identification renforcée hors OTP |
Onboarding industriel et distributeur : policy qualified (QES), avec ordered_signers=true (contrainte API Yousign pour QES).
Règles par défaut :
| Type de document | Politique par défaut |
|---|---|
sepa_mandate | required |
contract (montant au-dessus du seuil) | required |
delegation_proof | required |
cgu | none |
| Autres | none |
Un override admin manuel est possible avec traçabilité obligatoire.
Mapping des statuts
Les statuts Yousign sont normalisés via app/mypromo/server/modules/esign-core/application/map-provider-status.ts :
| Statut Yousign | Statut interne (ESignStatus) |
|---|---|
draft | draft |
approval, ongoing | pending_signature |
done | signed |
declined | declined |
expired | expired |
canceled | canceled |
rejected | canceled |
Statuts internes complets : pending_creation, draft, pending_signature, signer_notified, signer_opened, signed, declined, expired, canceled, invalidated, error.
Les statuts pending_creation, draft, pending_signature, signer_notified, signer_opened sont considérés "en attente" (isPendingESignStatus). Tant qu'une demande onboarding est en attente, l'accès workspace est bloqué sur /onboarding-signature-pending.
Webhooks
Endpoint
POST /api/webhooks/yousign
Sécurité :
- Vérification HMAC SHA-256 du payload brut via l'en-tête
X-Yousign-Signature-256. - Vérification de fraîcheur via
X-Yousign-Issued-At. - Déduplication stricte par
event_id(tableauth_signature_webhook_events). - ACK immédiat (< 1s) ; traitement métier en job asynchrone.
Événements à souscrire
signature_request.activated
signature_request.done
signature_request.declined
signature_request.expired
signature_request.canceled
signer.notified
signer.link_opened
signer.done
signer.declined
signer.error
signer.notification_delivery_failed
Réconciliation
Un service de réconciliation (signature-reconciliation) interroge l'API Yousign par polling pour corriger les événements manquants. La réconciliation est déclenchée à la demande lors de la résolution de l'état de signature onboarding (option allowExternalReconciliation).
Variables d'environnement
| Variable | Usage |
|---|---|
YOUSIGN_API_KEY | Clé API Yousign (stockée côté serveur uniquement) |
YOUSIGN_BASE_URL | URL de base Yousign (https://api-sandbox.yousign.app/v3 en sandbox, https://api.yousign.app/v3 en production) |
YOUSIGN_WEBHOOK_SECRET | Secret partagé pour la vérification HMAC des webhooks |
Ces valeurs ne sont jamais exposées côté client ni loguées.
Configuration sandbox (dev)
1. Variables d'environnement
Dans le fichier .env du projet :
YOUSIGN_API_KEY=<votre-cle-sandbox>
YOUSIGN_BASE_URL=https://api-sandbox.yousign.app/v3
YOUSIGN_WEBHOOK_SECRET=<votre-secret-webhook>
2. Déclaration du webhook
- Exposer l'application localement sur une URL HTTPS publique (ngrok, cloudflared).
- Dans le dashboard Yousign Sandbox, créer une subscription webhook vers :
POST https://<votre-domaine>/api/webhooks/yousign - Configurer le secret webhook identique à
YOUSIGN_WEBHOOK_SECRET. - Souscrire les événements listés ci-dessus.
3. Automatisation DX (ngrok + sync)
pnpm run dev:yousign
Ce script :
- Démarre un tunnel ngrok sur le port Nuxt local.
- Récupère l'URL HTTPS publique.
- Crée ou met à jour la subscription Yousign vers
https://<ngrok>/api/webhooks/yousign. - Lance
pnpm run dev.
Variables optionnelles :
| Variable | Défaut | Usage |
|---|---|---|
YOUSIGN_WEBHOOK_SUBSCRIPTION_ID | auto-detect | Force la mise à jour d'une subscription précise |
YOUSIGN_DEV_NGROK_BIN | ngrok | Chemin vers le binaire ngrok |
YOUSIGN_DEV_NGROK_API_URL | http://127.0.0.1:4040/api/tunnels | API locale ngrok |
YOUSIGN_DEV_NGROK_TIMEOUT_MS | 30000 | Timeout de détection URL publique |
4. Vérification
- Lancer l'application (
pnpm run dev). - Créer une signature request via le parcours onboarding ou un endpoint interne.
- Simuler une complétion dans le dashboard Yousign Sandbox.
- Vérifier en base :
auth_signature_webhook_events: événement reçu avecprocessingStatus=processedauth_signature_requests:statusmis à jour (ex.signed)
Mandat d'autofacturation (Phase 5 — module billing)
Le module billing réutilise le module esign-core pour gérer un flux de signature dédié : le mandat d'autofacturation signé par le distributeur (article 289 du CGI). Ce mandat conditionne l'émission automatique des autofactures fournisseur (supplier_self_billed) après collecte.
Différences avec l'onboarding
- Persistance : les mandats sont stockés dans
billing_self_billing_mandates(modèle PrismaBillingSelfBillingMandate), pas dansauth_signature_requests. Machine à états spécifique :draft → pending_signature → signed | declined ; signed → revoked— statuts terminauxdeclinedetrevoked. - Provider : le même
YousignESignProviderd'authest réutilisé — le module billing y accède via le barrel publicauth/index.ts(frontière modules respectée, résolution dynamique dansbilling/application/self-billing-mandate-signature.ts). - Document : PDF généré par
billing/application/self-billing-mandate-document.ts(pdf-lib direct — pas de template markdown versionné). Stocké dans le contexte S3 privébilling-self-billing-mandate(/billing/self-billing-mandates, 5 Mo max). - Niveau de signature :
electronic_signaturepar défaut (le mandat porte un enjeu financier modéré ; passer enadvanced_electronic_signaturereste possible via l'orchestrateur si la politique produit évolue). - Unicité : un seul mandat actif (
pending_signatureousigned) par organisation, garanti par un index partiel PostgreSQL + une garde applicative renvoyant 409.
Routage du webhook
Le webhook Yousign POST /api/webhooks/yousign est unique, mais fan-oute désormais vers les deux modules :
parseYousignWebhookIdentity(payload)(publiée dans le barrelauth) extraitevent_id,event_name,providerSignatureRequestId.processYousignWebhook(...)(auth) traite le match desAuthSignatureRequestet persiste l'événement (dédup par event_id).- Si l'événement n'est pas un duplicate →
applyYousignEventToSelfBillingMandate(...)(billing) tente de matcher un mandat billing paresignRequestRefet applique la transition. NE throw JAMAIS. Idempotence portée par la validation des transitions (mandat déjàsigned→signedNO-OP).
Voir aussi 2.modules/3.facturation.md § « Autofacturation » pour le cycle complet et les endpoints admin/GET (/api/billing/organizations/{organizationId}/self-billing-mandate).
Supervision admin
/admin/onboarding-signatures— liste des envelopes onboarding avec possibilité de relancer les envelopes en échec.- Admin bloqué à approuver une organisation tant que l'enveloppe onboarding n'est pas
signed. Si le signataire n'est pas au KBIS, les trois documents de délégation doivent aussi être présents.
Erreurs courantes
| Erreur | Cause probable |
|---|---|
401 AUTH_SIGNATURE_WEBHOOK_INVALID | Signature HMAC invalide ou secret non aligné |
Yousign credentials are invalid... | API key invalide ou permissions insuffisantes |
| Events reçus mais statut non mis à jour | providerSignatureRequestId absent sur le dossier |
Politique de retry Yousign
Yousign effectue jusqu'à 8 retries automatiques (backoff progressif). Le header X-Yousign-Retry indique le numéro de tentative. Timeout : 1s sur le premier envoi, 10s sur les retries. L'endpoint webhook doit répondre sous 1s pour éviter les timeouts.
Points d'entrée code
| Composant | Chemin |
|---|---|
| Types esign | app/mypromo/server/modules/esign-core/domain/types.ts |
Port ESignProvider | app/mypromo/server/modules/esign-core/domain/ports.ts |
| Mapping statuts | app/mypromo/server/modules/esign-core/application/map-provider-status.ts |
| Orchestrateur signature onboarding | app/mypromo/server/modules/auth/application/signature-orchestrator.ts |
| Handler webhook (fan-out auth+billing) | app/mypromo/server/api/webhooks/yousign.post.ts |
| Repository signatures onboarding | app/mypromo/server/modules/auth/infrastructure/signature-request.repository.ts |
| Politique signature onboarding | app/mypromo/server/modules/auth/domain/signature-policy.ts |
| Domaine mandat autofacturation | app/mypromo/server/modules/billing/domain/self-billing-mandate-rules.ts |
| Application mandat autofacturation | app/mypromo/server/modules/billing/application/self-billing-mandate.ts |
| Webhook application (billing) | app/mypromo/server/modules/billing/application/self-billing-mandate-webhook.ts |
| Génération PDF mandat | app/mypromo/server/modules/billing/application/self-billing-mandate-document.ts |
| Repository mandat | app/mypromo/server/modules/billing/infrastructure/self-billing-mandate-repository.ts |