Signature électronique (Yousign)

Intégration Yousign API v3 — flux de signature, niveaux de vérification, webhooks, configuration sandbox et exploitation.

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 — interface ESignProvider (port)
  • domain/types.ts — types ESignStatus, ESignSignatureLevel, ESignAuthenticationMode
  • application/map-provider-status.ts — mapping statuts Yousign → statuts internes
  • application/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

  1. Les données signataire sont lues depuis organization_billing_profiles via le read model d'onboarding canonique.
  2. Le backend génère le PDF de chaque document depuis son template markdown rendu via le catalogue DocumentTemplate.
  3. 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.locale est envoyé explicitement (fr).
  • Pour otp_sms, le numéro est normalisé en E.164 (fallback 0X…+33X…).
  • Au moins un champ de signature par signataire est fourni avec position explicite sur la page 1.
  • Upload en multipart/form-data avec parse_anchors=true ; en cas d'erreur 400, retry en parse_anchors=false puis 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) : le signature_link est récupéré pour diffusion in-app.

Niveaux de vérification d'identité

Une politique interne identityVerificationPolicy contrôle le niveau Yousign par dossier :

PolitiqueNiveau YousignMode auth
noneelectronic_signatureno_otp, otp_email ou otp_sms
requiredadvanced_electronic_signatureotp_sms (obligatoire AES)
qualifiedqualified_electronic_signatureidentification 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 documentPolitique par défaut
sepa_mandaterequired
contract (montant au-dessus du seuil)required
delegation_proofrequired
cgunone
Autresnone

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 YousignStatut interne (ESignStatus)
draftdraft
approval, ongoingpending_signature
donesigned
declineddeclined
expiredexpired
canceledcanceled
rejectedcanceled

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 (table auth_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

VariableUsage
YOUSIGN_API_KEYClé API Yousign (stockée côté serveur uniquement)
YOUSIGN_BASE_URLURL de base Yousign (https://api-sandbox.yousign.app/v3 en sandbox, https://api.yousign.app/v3 en production)
YOUSIGN_WEBHOOK_SECRETSecret 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

  1. Exposer l'application localement sur une URL HTTPS publique (ngrok, cloudflared).
  2. Dans le dashboard Yousign Sandbox, créer une subscription webhook vers :
    POST https://<votre-domaine>/api/webhooks/yousign
    
  3. Configurer le secret webhook identique à YOUSIGN_WEBHOOK_SECRET.
  4. Souscrire les événements listés ci-dessus.

3. Automatisation DX (ngrok + sync)

pnpm run dev:yousign

Ce script :

  1. Démarre un tunnel ngrok sur le port Nuxt local.
  2. Récupère l'URL HTTPS publique.
  3. Crée ou met à jour la subscription Yousign vers https://<ngrok>/api/webhooks/yousign.
  4. Lance pnpm run dev.

Variables optionnelles :

VariableDéfautUsage
YOUSIGN_WEBHOOK_SUBSCRIPTION_IDauto-detectForce la mise à jour d'une subscription précise
YOUSIGN_DEV_NGROK_BINngrokChemin vers le binaire ngrok
YOUSIGN_DEV_NGROK_API_URLhttp://127.0.0.1:4040/api/tunnelsAPI locale ngrok
YOUSIGN_DEV_NGROK_TIMEOUT_MS30000Timeout de détection URL publique

4. Vérification

  1. Lancer l'application (pnpm run dev).
  2. Créer une signature request via le parcours onboarding ou un endpoint interne.
  3. Simuler une complétion dans le dashboard Yousign Sandbox.
  4. Vérifier en base :
    • auth_signature_webhook_events : événement reçu avec processingStatus=processed
    • auth_signature_requests : status mis à 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 Prisma BillingSelfBillingMandate), pas dans auth_signature_requests. Machine à états spécifique : draft → pending_signature → signed | declined ; signed → revoked — statuts terminaux declined et revoked.
  • Provider : le même YousignESignProvider d'auth est réutilisé — le module billing y accède via le barrel public auth/index.ts (frontière modules respectée, résolution dynamique dans billing/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_signature par défaut (le mandat porte un enjeu financier modéré ; passer en advanced_electronic_signature reste possible via l'orchestrateur si la politique produit évolue).
  • Unicité : un seul mandat actif (pending_signature ou signed) 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 :

  1. parseYousignWebhookIdentity(payload) (publiée dans le barrel auth) extrait event_id, event_name, providerSignatureRequestId.
  2. processYousignWebhook(...) (auth) traite le match des AuthSignatureRequest et persiste l'événement (dédup par event_id).
  3. Si l'événement n'est pas un duplicate → applyYousignEventToSelfBillingMandate(...) (billing) tente de matcher un mandat billing par esignRequestRef et applique la transition. NE throw JAMAIS. Idempotence portée par la validation des transitions (mandat déjà signedsigned NO-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

ErreurCause probable
401 AUTH_SIGNATURE_WEBHOOK_INVALIDSignature HMAC invalide ou secret non aligné
Yousign credentials are invalid...API key invalide ou permissions insuffisantes
Events reçus mais statut non mis à jourproviderSignatureRequestId 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

ComposantChemin
Types esignapp/mypromo/server/modules/esign-core/domain/types.ts
Port ESignProviderapp/mypromo/server/modules/esign-core/domain/ports.ts
Mapping statutsapp/mypromo/server/modules/esign-core/application/map-provider-status.ts
Orchestrateur signature onboardingapp/mypromo/server/modules/auth/application/signature-orchestrator.ts
Handler webhook (fan-out auth+billing)app/mypromo/server/api/webhooks/yousign.post.ts
Repository signatures onboardingapp/mypromo/server/modules/auth/infrastructure/signature-request.repository.ts
Politique signature onboardingapp/mypromo/server/modules/auth/domain/signature-policy.ts
Domaine mandat autofacturationapp/mypromo/server/modules/billing/domain/self-billing-mandate-rules.ts
Application mandat autofacturationapp/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 mandatapp/mypromo/server/modules/billing/application/self-billing-mandate-document.ts
Repository mandatapp/mypromo/server/modules/billing/infrastructure/self-billing-mandate-repository.ts