Aller au contenu
Documentation

Signer le lot

Autoriser le lot par l'attestation, le code et ce avec quoi le signataire signe, puis préparer et signer ses parcours par paquets de dix.

6 min de lecture Mise à jour le 9 novembre 2026

Le signataire a lu la formule de consentement et, quand le niveau en demande un, reçu son code. La signature se fait en trois temps :

  1. autoriser : l'attestation, le code, et ce avec quoi le signataire signe — une clé par parcours, ou son certificat qualifié ;
  2. préparer, par dix : chaque parcours rend ce que la clé du signataire doit signer, document par document ;
  3. signer, par dix : la clé signe, et chaque parcours reçoit sa signature.

1Ce avec quoi le signataire signe

  • Signature avancée (SignatureLevel 1) : pour chaque parcours du lot, une paire de clés ECDSA P-256 générée sur le poste ou l'appareil du signataire, non exportable ; sa clé publique au format SPKI (DER, encodé en base64). Une clé ne sert qu'à un parcours, et à une seule signature.
  • Signature qualifiée (2) et avancée avec certificat qualifié (3) : le certificat qualifié du signataire, sur sa carte ou sa clé ; sa chaîne, chaque certificat en DER encodé en base64, le sien en tête. Votre logiciel fait signer sa clé par le système du poste, qui demande au signataire son code PIN. En signature qualifiée, le certificat doit être sur un dispositif qualifié (RequiresQualifiedDevice).

2Autoriser

POST https://staging-api.docage.com/Transactions/ConsentBatches/abababab-0000-4000-8000-000000000009/Authorize
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…
Content-Type: application/json

{
  "Code": "482913",
  "ConsentAccepted": true,
  "ConsentSha256": "9f2c5e…",
  "Keys": [
    { "TransactionId": "cccccccc-0000-4000-8000-000000000001", "PublicKeySpki": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE…" },
    { "TransactionId": "cccccccc-0000-4000-8000-000000000004", "PublicKeySpki": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE…" }
  ]
}
  • Code : le code saisi par le signataire (les espaces sont ignorés) ; vide quand le niveau n'en demande pas.
  • ConsentAccepted : true, le signataire a accepté la formule.
  • ConsentSha256 : l'empreinte SHA-256, en hexadécimal, de la formule telle que vous l'avez montrée — ConsentText, chaque suite de blancs réduite à une espace, sans blanc au début ni à la fin, encodé en UTF-8.
  • Keys (signature avancée) : une clé publique par parcours du lot, tous les parcours que le code autorise, toutes distinctes.
  • CertificateChain (niveaux à certificat) : la chaîne du certificat, à la place de Keys : "CertificateChain": ["MIIF…", "MIIE…"].

Ce avec quoi le signataire signe est jugé avant que le code soit consommé — un certificat refusé ne le brûle pas. La réponse :

JSON
{
  "BatchId": "abababab-0000-4000-8000-000000000009",
  "TransactionIds": ["cccccccc-0000-4000-8000-000000000001", "cccccccc-0000-4000-8000-000000000004"],
  "ExpiresAtUtc": "2026-11-09T11:16:40.000Z"
}

Le lot reste autorisé une heure : le temps de préparer et de signer ses parcours.

3Préparer, par dix

POST https://staging-api.docage.com/Transactions/ConsentBatches/abababab-0000-4000-8000-000000000009/Prepare
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…
Content-Type: application/json

{ "TransactionIds": ["cccccccc-0000-4000-8000-000000000001", "cccccccc-0000-4000-8000-000000000004"] }
JSON
{
  "Prepared": [
    {
      "TransactionId": "cccccccc-0000-4000-8000-000000000001",
      "PreparationToken": "q3Jd…",
      "CertificatePem": "-----BEGIN CERTIFICATE-----\nMII…\n-----END CERTIFICATE-----",
      "SignatureAlgorithm": "ECDSA",
      "Items": [ { "TransactionFileId": "eeeeeeee-0000-4000-8000-000000000005", "SignedAttributes": "MWow…" } ],
      "ExpiresAtUtc": "2026-11-09T10:27:40.000Z"
    }
  ],
  "Failed": [
    { "Id": "cccccccc-0000-4000-8000-000000000004", "Result": "Le document « Avenant » a changé depuis l'envoi du code : relisez-le avant de signer ce parcours" }
  ]
}

Chaque parcours préparé rend son jeton (PreparationToken), le certificat du signataire, l'algorithme à employer (SignatureAlgorithm) et un élément par document : SignedAttributes, en base64, ce que la clé doit signer. Failed dit les parcours écartés et pourquoi.

En signature avancée, le certificat est émis sur la clé du parcours et vaut une dizaine de minutes (ExpiresAtUtc) : signez chaque paquet aussitôt préparé. Aux niveaux à certificat, la préparation vaut jusqu'à la fin du lot : préparez tout, faites signer le dispositif en une fois, puis signez par paquets.

4Signer, par dix

POST https://staging-api.docage.com/Transactions/ConsentBatches/abababab-0000-4000-8000-000000000009/Complete
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…
Content-Type: application/json

{
  "Transactions": [
    {
      "TransactionId": "cccccccc-0000-4000-8000-000000000001",
      "PreparationToken": "q3Jd…",
      "Signatures": [ { "TransactionFileId": "eeeeeeee-0000-4000-8000-000000000005", "Signature": "o9Kc…" } ]
    }
  ]
}

Signature est la signature, par la clé du parcours ou du certificat, des octets de SignedAttributes décodés du base64 — tels quels, sans les hacher vous-même —, encodée en base64 : ECDSA avec SHA-256 (les 64 octets r‖s que rend WebCrypto, ou la forme DER), ou RSA PKCS#1 v1.5 avec SHA-256 quand SignatureAlgorithm vaut RSA. Une signature par document préparé ; dix parcours au plus par requête.

JSON
[ { "Id": "cccccccc-0000-4000-8000-000000000001", "Result": "Success" } ]

Success, ou la raison, parcours par parcours. Chaque document reçoit alors sa signature, horodatée, et le parcours continue : actions suivantes, emails, clôture.

En JavaScript (signature avancée)

Dans l'application du signataire — jamais sur un serveur qui signerait à sa place. Ce code tourne tel quel dans Node.js ou une application de bureau ; dans une page web d'un autre domaine, que l'API ne sert pas, faites passer appeler par votre serveur, les clés restant dans le navigateur :

JavaScript
const api = 'https://staging-api.docage.com';
const appeler = async (methode, route, corps) => {
  const reponse = await fetch(`$https://staging-api.docage.com/${route}`, {
    method: methode,
    headers: { Authorization: `Bearer ${jeton}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(corps ?? {}),
  });
  if (!reponse.ok) throw new Error(`${reponse.status} ${await reponse.text()}`);
  return reponse.json();
};
const base64 = octets => btoa(String.fromCharCode(...new Uint8Array(octets)));
const octetsDe = texte => Uint8Array.from(atob(texte), c => c.charCodeAt(0));
const paquets = (liste, taille) => Array.from({ length: Math.ceil(liste.length / taille) }, (_, i) => liste.slice(i * taille, (i + 1) * taille));

// jeton : celui de la session du signataire ; lot : la réponse de ConsentBatches ; envoi : celle de SendCode ;
// code : saisi par le signataire, qui a accepté lot.ConsentText
const cles = new Map();
const Keys = [];
for (const id of envoi.TransactionIds) {
  const paire = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
  cles.set(id, paire);
  Keys.push({ TransactionId: id, PublicKeySpki: base64(await crypto.subtle.exportKey('spki', paire.publicKey)) });
}
const formule = lot.ConsentText.replace(/\s+/g, ' ').trim();
const empreinte = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(formule)));
const ConsentSha256 = [...empreinte].map(o => o.toString(16).padStart(2, '0')).join('');
const route = `Transactions/ConsentBatches/${lot.BatchId}`;

const autorisation = await appeler('POST', `${route}/Authorize`, { Code: code, ConsentAccepted: true, ConsentSha256, Keys });
for (const TransactionIds of paquets(autorisation.TransactionIds, 10)) {
  const preparation = await appeler('POST', `${route}/Prepare`, { TransactionIds });
  const Transactions = [];
  for (const parcours of preparation.Prepared) {
    const Signatures = [];
    for (const element of parcours.Items) {
      const signature = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' },
        cles.get(parcours.TransactionId).privateKey, octetsDe(element.SignedAttributes));
      Signatures.push({ TransactionFileId: element.TransactionFileId, Signature: base64(signature) });
    }
    Transactions.push({ TransactionId: parcours.TransactionId, PreparationToken: parcours.PreparationToken, Signatures });
  }
  const resultats = await appeler('POST', `${route}/Complete`, { Transactions });
  // resultats : « Success », ou la raison, parcours par parcours ; preparation.Failed : les parcours écartés
}

En C#, la même clé se crée par ECDsa.Create(ECCurve.NamedCurves.nistP256), sa clé publique par ExportSubjectPublicKeyInfo(), et la signature par SignData(octets, HashAlgorithmName.SHA256, DSASignatureFormat.IeeeP1363FixedFieldConcatenation) ; avec un certificat qualifié, certificat.GetRSAPrivateKey().SignData(octets, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1).

Les refus

À l'autorisation, 400 :

Message Ce qu'il faut faire
« Le code de sécurité est incorrect » Le ressaisir : cinq essais au plus.
« Trop de tentatives : demandez un nouveau code de sécurité » Renvoyer un code.
« Le code de sécurité a expiré, demandez-en un nouveau » Renvoyer un code.
« La formule de consentement affichée n'est pas celle du service : rouvrez la signature avant de signer » Montrer ConsentText tel qu'il a été rendu, et en recalculer l'empreinte.
« Chaque parcours du lot se signe avec sa propre clé : une clé publique distincte par parcours est attendue » Une clé neuve par parcours, pour tous les parcours du lot.
« Le certificat présenté n'est pas au nom de … » Le certificat du signataire lui-même.
« Le certificat présenté ne permet pas une signature qualifiée : … » Un certificat qualifié sur dispositif qualifié.
« Ce lot est déjà autorisé » Le lot s'est déjà autorisé : préparez et signez ses parcours.

À la préparation et à la signature, la raison d'un parcours dans Failed ou dans Result : « Le document « … » a changé depuis l'envoi du code : relisez-le avant de signer ce parcours », « La signature reçue ne correspond pas au certificat du signataire », « Le jeton de préparation est invalide », « La préparation de la signature a expiré : demandez un nouveau code »…

Les pièges

  • Vérifiez vos clés avant d'autoriser : une clé publique illisible n'est refusée qu'à la préparation, le code déjà consommé — il faut alors signer ce parcours depuis son invitation.
  • Un document modifié depuis le relevé — par la signature d'un autre participant, par exemple — fait écarter son parcours.
  • Signez chaque parcours préparé avant son ExpiresAtUtc. Une signature refusée ou un jeton invalide laissent la préparation en place : vous pouvez recommencer jusqu'à cette échéance.
  • La preuve garde l'adresse que la requête annonce (la première de X-Forwarded-For, sinon celle de la connexion), son navigateur et sa langue : un serveur relais transmet l'adresse du signataire dans X-Forwarded-For, sinon c'est la sienne que la preuve garde.
  • Un navigateur ne peut pas appeler l'API depuis une page d'un autre domaine : la signature se fait dans l'application du signataire, ou par votre propre serveur relais — les clés restant chez le signataire.