Aller au contenu

Intégrer la signature sur votre application

Ce guide décrit l'intégration complète du flux embarqué (invitation_mode="caller_embed") côté front : création de la demande, intégration de l'<iframe>, écoute des événements postMessage, rafraîchissement de jeton et gestion d'erreurs.

Si vous découvrez VoidSign ou ce mode, commencez plutôt par Première Requête (flux embarqué) puis revenez ici.

Ce que vous allez construire

Une page de votre application contient une <iframe> dont la source est une URL VoidSign à usage unique. Le signataire consulte le document, valide son code OTP et signe sans quitter votre interface. À la fin de la signature, votre code JavaScript reçoit un événement postMessage qui lui permet de poursuivre votre propre parcours (passer à l'étape suivante, afficher un message, déclencher un appel back-end).

sequenceDiagram
    participant U as Votre app (parent)
    participant B as Votre backend
    participant V@{ "type": "entity" } as VoidSign API

    U->>B: Demande de signer le document
    B->>V: POST /api/v1/requests (caller_embed)
    V-->>B: 201 { request_id, signers[].embed.iframe_url }
    B-->>U: iframe_url
    U->>V: <iframe src="…/sign/embed/<token>"><br/>(rendu PDF, OTP, finalisation)
    V-->>U: postMessage voidsign:signed
    U->>B: Notifie la fin de signature pour ce signataire
    Note over U: Avance dans votre tunnel UI
    V->>B: POST webhook: request.completed
    B-->>V: 200 OK
    B->>V: GET /downloads/signed
    B->>V: GET /downloads/audit-trail/pdf

voidsign:signed est un signal navigateur par signataire, pas un remplaçant du webhook

Dans le flux embarqué, l'événement postMessage voidsign:signed vous notifie en temps réel, côté navigateur, qu'un signataire vient de finaliser sa signature, dès qu'il signe. C'est plus granulaire que le webhook request.completed, qui n'est émis que lorsque tous les signataires ont signé : utilisez-le pour piloter votre UI signataire par signataire (avancer dans votre tunnel, afficher une confirmation) et, si vous le souhaitez, déclencher une mise à jour optimiste côté serveur. Il ne remplace pas le webhook pour autant : le signataire peut fermer son onglet avant l'émission du message, et un postMessage n'est pas une source de vérité fiable côté serveur. Réservez votre traitement métier (téléchargement, facturation, archivage) au webhook request.completed. Voir Passer en production.

Créer une demande pour le flux embarqué

Le corps de la requête est identique à celui du flux mail, à un seul champ près : invitation_mode="caller_embed". Aucun e-mail d'invitation n'est envoyé ; à la place, la réponse contient pour chaque signataire un bloc embed avec l'URL à intégrer.

curl -X POST \
  "https://api.voidsign.com/api/v1/requests" \
  -H "X-VoidSign-Key: vs_live_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "auth_method": "mail",
    "invitation_mode": "caller_embed",
    "locales": ["fr-FR"],
    "description": "Onboarding : contrat de service",
    "documents": [
      {
        "base64": "<BASE64_CONTENT>",
        "fields": [
          {
            "type": "signature",
            "box_coordinate_system": "CS-ULP-GD",
            "box_x_ratio": 0.15,
            "box_y_ratio": 0.7,
            "box_width_ratio": 0.3,
            "box_height_ratio": 0.1,
            "page_number": 0,
            "signer_ref": 1
          }
        ]
      }
    ],
    "signers": [
      {
        "ref": 1,
        "email": "jean.dupont@example.com",
        "first_name": "Jean",
        "last_name": "Dupont"
      }
    ]
  }'

Réponse 201 :

{
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "created",
  "signers": [
    {
      "signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "email": "jean.dupont@example.com",
      "status": "pending",
      "embed": {
        "iframe_url": "https://app.voidsign.com/sign/embed/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "expires_at": "2026-05-26T16:00:00+00:00"
      }
    }
  ]
}

Champs clés à conserver côté backend :

  • request_id : identifiant de la demande.
  • signers[i].signer_id : identifiant canonique de chaque signataire (à utiliser pour le rafraîchissement de jeton).
  • signers[i].embed.iframe_url : URL à transmettre au navigateur du signataire.
  • signers[i].embed.expires_at : date d'expiration du jeton sous-jacent.

Embarquer l'iframe dans votre page

Insérez une balise <iframe> dans la page de votre application au moment où vous voulez présenter la signature au signataire :

<iframe
  src="https://app.voidsign.com/sign/embed/<token>"
  width="600"
  height="800"
  style="border: 0;"
  allow="clipboard-write"
></iframe>

Recommandations :

  • Dimensions : utilisez des valeurs absolues, par exemple width="600" et height="800", pour garantir une prévisualisation du document et une zone de signature confortables.
  • allow : la valeur clipboard-write est utile pour permettre à vos signataires de copier/coller l'OTP.

Écouter la fin de la signature (postMessage)

VoidSign communique avec votre page parente par window.parent.postMessage. Deux types d'événements sont émis :

type Quand Charge utile
voidsign:signed Le signataire vient de finaliser sa signature. { type, request_id, signer_id }
voidsign:error Une erreur s'est produite. { type, code, reason, request_id?, signer_id? }

Installez un écouteur unique qui vérifie l'origine du message avant de le traiter :

window.addEventListener("message", (e) => {
  if (e.origin !== "https://app.voidsign.com") return;

  if (e.data?.type === "voidsign:signed") {
    // Le signataire a terminé : avancer dans votre UI,
    // déclencher un appel back-end, etc.
    advanceMyUi(e.data.request_id, e.data.signer_id);
  } else if (e.data?.type === "voidsign:error") {
    if (e.data.code === "recoverable") {
      // Le jeton est expiré ou il y a eu un incident transitoire.
      // Rafraîchissez le jeton et réintégrez l'iframe.
      refreshTokenAndReembed();
    } else {
      // Erreur terminale : la session est morte, prévenez l'utilisateur.
      showTerminalError(e.data.reason);
    }
  }
});

Vérifiez toujours event.origin

Tout site peut envoyer un postMessage à votre page. Sans vérification d'origine, un acteur malveillant pourrait simuler un événement voidsign:signed depuis une autre fenêtre. Comparez event.origin à https://app.voidsign.com (origine exacte, pas une sous-chaîne).

Rafraîchir le jeton

Le jeton encodé dans iframe_url expire au bout d'environ 2 heures. Pour rouvrir l'iframe au-delà (par exemple si l'utilisateur revient le lendemain), ne recréez pas la demande : appelez le endpoint de rafraîchissement de jeton.

curl -X POST \
  "https://api.voidsign.com/api/v1/requests/{request_id}/signers/{signer_id}/iframe_token" \
  -H "X-VoidSign-Key: vs_live_API_KEY" \
  -H "Content-Type: application/json"

Réponse 201 :

{
  "iframe_url": "https://app.voidsign.com/sign/embed/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_at": "2026-05-26T18:00:00+00:00"
}

Pour utiliser la nouvelle URL, mettez à jour la propriété src de votre <iframe> (la remplacer côté DOM suffit, le navigateur recharge la page embarquée). Voir Refresh iframe token pour la spécification complète.

Référence : gestion des erreurs

Quand voidsign:error est émis, deux codes existent :

  • code: "recoverable" : l'erreur peut être levée par un rafraîchissement de jeton et une ré-intégration de l'iframe.
  • code: "terminal" : la session est morte, un rafraîchissement n'aidera pas.

Le champ reason est une chaîne libre, destinée à des logs lisibles et susceptible d'être enrichie. Le tableau ci-dessous regroupe les valeurs courantes. Traitez tout reason inconnu comme récupérable si code: "recoverable", terminal sinon.

code reason Que faire ?
recoverable token_expired Appelez le endpoint de rafraîchissement, remplacez iframe.src.
recoverable network Réintégrez l'iframe avec le même jeton (s'il n'est pas expiré) ou rafraîchissez.
recoverable transient_server Réessayez la même URL après un court délai (environ 5 s). Rafraîchissez si l'erreur persiste.
terminal request_cancelled La demande a été annulée côté entreprise. Informez l'utilisateur, ne tentez pas de rouvrir.
terminal already_signed Le signataire avait déjà signé dans une session précédente. Passez à l'étape suivante de votre UI.
terminal otp_lockout 5 tentatives OTP erronées : l'accès est verrouillé. Demandez à l'utilisateur de réessayer plus tard.
terminal internal Erreur non catégorisée. Affichez un message d'erreur générique et journalisez les valeurs reçues.

Erreur côté API : iframe_token_not_supported

Si vous appelez le endpoint de rafraîchissement de jeton sur une demande créée en voidsign_mail, l'API répond avec l'erreur iframe_token_not_supported. Le rafraîchissement n'est valable que pour les demandes créées en caller_embed.