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"etheight="800", pour garantir une prévisualisation du document et une zone de signature confortables. allow: la valeurclipboard-writeest 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.