Passer au contenu principal

Éditeur de demande de signature intégrable

Intégrez l’éditeur de demande de signature de Firma dans votre application grâce à une authentification JWT pour un accès sécurisé et limité dans le temps. C’est idéal pour les intégrations en marque blanche et les applications multi-tenant.

Cas d’usage

  • Flux de signature en marque blanche : gérez les demandes de signature sous votre propre marque
  • Applications multi-tenant : accès sécurisé par signataire sans exposer vos clés API
  • Flux documentaires intégrés : gestion fluide des demandes de signature au sein de votre produit
  • Accès limité dans le temps : les jetons expirent automatiquement pour plus de sécurité (expiration à 7 jours)

Fonctionnement

  1. Votre serveur demande un jeton JWT à l’API de Firma en utilisant votre clé API
  2. Firma renvoie un jeton JWT de courte durée contenant l’identifiant de la demande de signature
  3. Votre frontend intègre l’éditeur avec le jeton JWT
  4. Le jeton expire automatiquement après 7 jours
Limite de débit : les endpoints JWT prennent en charge 100 requêtes par minute et par clé API pour les applications à fort volume.

Authentification JWT

Générer un jeton JWT

Générez un jeton JWT pour une demande de signature spécifique à l’aide de l’endpoint /jwt/generate-signing-request. Endpoint : POST /jwt/generate-signing-request Corps de la requête :
{
  "companies_workspaces_signing_requests_id": "123e4567-e89b-12d3-a456-426614174000"
}
Réponse (200 OK) :
{
  "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "jwt_id": "jwt123-e89b-12d3-a456-426614174000",
  "expires_at": "2024-04-27T10:00:00Z",
  "signing_request_id": "123e4567-e89b-12d3-a456-426614174000",
  "created_at": "2024-04-20T10:00:00Z"
}
En-têtes de limite de débit :
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1729180800

Guide d’implémentation

Sécurité : n’exposez jamais votre clé API dans du code côté client. Générez toujours les jetons JWT depuis votre backend sécurisé.

Backend : générer un jeton JWT

Appelez l’API Firma depuis votre backend pour générer un jeton JWT. Votre endpoint backend doit accepter un identifiant de demande de signature et renvoyer le JWT à votre frontend. Exemple Node.js :
// Example: Simple backend function to generate JWT
async function generateSigningRequestToken(signingRequestId) {
  const response = await fetch('https://api.firma.dev/functions/v1/signing-request-api/jwt/generate-signing-request', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.FIRMA_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      companies_workspaces_signing_requests_id: signingRequestId
    })
  })
  
  const data = await response.json()
  return data.jwt
}
Exemple Python :
import os
import requests

def generate_signing_request_token(signing_request_id):
    response = requests.post(
        'https://api.firma.dev/functions/v1/signing-request-api/jwt/generate-signing-request',
        headers={
            'Authorization': f'Bearer {os.getenv("FIRMA_API_KEY")}',
            'Content-Type': 'application/json'
        },
        json={
            'companies_workspaces_signing_requests_id': signing_request_id
        }
    )
    data = response.json()
    return data['jwt']

Implémentation frontend — HTML / JavaScript natif

<!-- Load the Firma Signing Request Editor library -->
<script src="https://api.firma.dev/functions/v1/embed-proxy/signing-request-editor.js"></script>

<!-- Create a container for the editor -->
<div id="signing-request-editor-container"></div>

<script>
  // Assuming you've already fetched the JWT from your backend
  const jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...';
  const signingRequestId = '08f72e3b-79a8-4eac-a268-b3f9efaf6573';
  
  // Initialize the signing request editor
  const editor = new FirmaSigningRequestEditor({
    container: '#signing-request-editor-container',
    jwt: jwt,
    signingRequestId: signingRequestId,
    showCloseButton: true, // Show close button in header
    onSave: (data) => {
      console.log('Signing request saved:', data);
    },
    onSend: (data) => {
      console.log('Signing request sent:', data);
    },
    onClose: (data) => {
      if (data.hasUnsavedChanges) {
        // Prompt user to save before leaving
      }
      console.log('Editor closed, unsaved changes:', data.hasUnsavedChanges);
    },
    onError: (error) => {
      console.error('Editor error:', error);
    },
    onLoad: (signingRequest) => {
      console.log('Editor loaded successfully:', signingRequest);
    }
  });
</script>

Implémentation frontend — React

import { useEffect, useRef, useState } from 'react';

function SigningRequestEditorComponent({ signingRequestId, jwt }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const editorRef = useRef<any>(null);
  const [isLoaded, setIsLoaded] = useState(false);

  // Load the Firma Signing Request Editor library
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://api.firma.dev/functions/v1/embed-proxy/signing-request-editor.js';
    script.async = true;
    
    script.onload = () => setIsLoaded(true);
    document.body.appendChild(script);
    
    return () => {
      if (document.body.contains(script)) {
        document.body.removeChild(script);
      }
    };
  }, []);

  // Initialize the editor when library and JWT are ready
  useEffect(() => {
    if (!isLoaded || !containerRef.current || !jwt) return;

    editorRef.current = new window.FirmaSigningRequestEditor({
      container: containerRef.current,
      jwt: jwt,
      signingRequestId: signingRequestId,
      showCloseButton: true, // Show close button in header
      onSave: (data) => console.log('Saved:', data),
      onSend: (data) => console.log('Sent:', data),
      onClose: (data) => {
        if (data.hasUnsavedChanges) {
          // Prompt user to save before leaving
        }
      },
      onError: (error) => console.error('Error:', error),
      onLoad: (signingRequest) => console.log('Loaded:', signingRequest)
    });

    return () => {
      if (editorRef.current?.destroy) {
        editorRef.current.destroy();
      }
    };
  }, [isLoaded, jwt, signingRequestId]);

  return <div ref={containerRef} className="w-full h-full" />;
}

export default SigningRequestEditorComponent;

Options de configuration

OptionTypeDescription
containerHTMLElementÉlément DOM sur lequel monter l’éditeur (obligatoire)
jwtstringJeton d’authentification provenant de votre backend (obligatoire)
signingRequestIdstringIdentifiant de la demande de signature (obligatoire)
theme'dark' | 'light'Thème de l’éditeur (par défaut : 'dark')
readOnlybooleanActive le mode lecture seule (par défaut : false)
heightstringHauteur du conteneur (par défaut : '100vh')
widthstringLargeur du conteneur (par défaut : '100%')
showCloseButtonbooleanAffiche un bouton de fermeture dans l’en-tête de l’éditeur (par défaut : false)
onSavefunctionCallback déclenché lorsque la demande de signature est enregistrée
onSendfunctionCallback déclenché lorsque la demande de signature est envoyée
onClosefunctionCallback déclenché lors du clic sur le bouton de fermeture ou de l’appel à triggerClose(). Reçoit { hasUnsavedChanges: boolean }
onErrorfunctionCallback pour la gestion des erreurs
onLoadfunctionCallback déclenché lorsque l’éditeur charge les données de la demande de signature

Méthodes d’instance

MéthodeDescription
triggerClose()Déclenche par programmation le flux de fermeture. Vérifie les modifications non enregistrées et invoque onClose avec { hasUnsavedChanges: boolean }
Utilisez triggerClose() lorsque votre application hôte doit fermer l’éditeur depuis sa propre interface, par exemple lors d’un événement de navigation parent ou d’un bouton « retour » :
// Close the editor from your app's UI
document.getElementById('my-back-button').addEventListener('click', () => {
  editor.triggerClose();
});

Événements postMessage (éditeur → hôte)

L’éditeur de demande de signature de Firma émet des événements postMessage pour les actions clés du cycle de vie. Voici un schéma d’événements minimal recommandé que vous pouvez implémenter pour réagir aux enregistrements et envois depuis l’éditeur.
Utilisez ces événements pour suivre l’activité de l’éditeur sans effectuer d’appels API supplémentaires, ce qui vous aide à rester dans les limites de débit.
Enveloppe de l’événement (charge utile de window.postMessage) :
{
	"type": "editor.event",
	"event": "signing_request.saved", // or signing_request.sent, signing_request.closed
	"payload": {
		"signing_request_id": "sr_123",
		"updated_at": "2025-09-04T12:34:56Z",
		"status": "draft" // or "sent"
	}
}

Exemple d’écouteur côté client (JS pur)

window.addEventListener('message', (ev) => {
	// 1) Validate origin
	if (ev.origin !== 'https://app.firma.dev') return
	// 2) Validate shape
	const data = ev.data || {}
	if (data.type !== 'editor.event') return

	switch (data.event) {
		case 'signing_request.saved':
			console.log('Signing request saved', data.payload)
			// Optionally fetch the latest signing request via API
			break
		case 'signing_request.sent':
			console.log('Signing request sent', data.payload)
			// Trigger webhook or notification
			break
		case 'signing_request.closed':
			console.log('Editor closed')
			break
		default:
			break
	}
})

Gestion du cycle de vie des jetons

Les jetons JWT sont générés avec une durée d’expiration de 7 jours pour les flux d’édition classiques. L’éditeur gère automatiquement l’expiration du jeton.

Expiration automatique

Les jetons JWT expirent automatiquement après 7 jours, en fonction du timestamp expires_at. Après expiration :
  • L’éditeur intégré rejette le jeton
  • Un nouveau jeton doit être demandé pour continuer
  • Aucun appel API n’est nécessaire, les jetons expirent passivement

Rafraîchissement du jeton (optionnel)

Pour les sessions d’édition de longue durée, vous pouvez rafraîchir le jeton avant son expiration à l’aide de la méthode updateJWT() :
// Generate a new JWT from your backend
const newJwt = await fetch('/your-backend/generate-token').then(r => r.json());

// Update the editor with the new token
editor.updateJWT(newJwt.jwt);

Limite de débit

Les endpoints JWT prennent en charge 100 requêtes par minute et par clé API. En-têtes de limite de débit :
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1729180800
Si vous dépassez la limite de débit (réponse 429) :
  • Mettez en cache les jetons et réutilisez-les jusqu’à leur expiration (7 jours)
  • Implémentez un backoff exponentiel pour les nouvelles tentatives
  • Surveillez l’en-tête X-RateLimit-Remaining
  • Générez les jetons à la demande, pas de manière anticipée

Bonnes pratiques de sécurité

N’exposez jamais votre clé API dans du code côté client. Générez toujours les jetons JWT depuis un endpoint serveur sécurisé.

✅ À faire

  • ✅ Générez les jetons depuis votre serveur backend
  • ✅ Surveillez les limites de débit (100 requêtes/minute)
  • ✅ Utilisez HTTPS pour toutes les requêtes API
  • ✅ Utilisez readOnly: true pour un accès en lecture seule

❌ À ne pas faire

  • ❌ N’exposez pas les clés API dans le code frontend
  • ❌ Ne réutilisez pas les jetons entre plusieurs sessions ou signataires
  • ❌ Ne journalisez pas les jetons JWT (risque de sécurité)
  • ❌ Ne partagez pas les jetons entre différentes demandes de signature

Dépannage

Erreur de jeton expiré

Symptôme : l’éditeur affiche « Token expired » ou une erreur d’authentification Solution :
  • Implémentez un rafraîchissement du jeton avant expiration (fenêtre de 7 jours)
  • Générez un nouveau jeton et appelez editor.updateJWT(newToken)
  • Vérifiez la synchronisation de l’horloge système

401 Unauthorized

Symptôme : la génération du JWT échoue avec une erreur 401 Causes possibles :
  • Clé API invalide ou manquante
  • La clé API ne dispose pas des permissions requises
  • La clé API est désactivée
Solution : vérifiez la clé API dans le tableau de bord et contrôlez les permissions

404 Not Found

Symptôme : la demande de signature est introuvable lors de la génération du JWT Causes possibles :
  • L’identifiant de la demande de signature n’existe pas
  • La demande de signature appartient à un autre espace de travail
  • La demande de signature a été supprimée
Solution : vérifiez l’identifiant de la demande de signature et l’accès à l’espace de travail

Limite de débit dépassée

Symptôme : 429 Too Many Requests Solution :
  • Implémentez une mise en cache des jetons (la fenêtre d’expiration de 7 jours est généreuse)
  • Attendez la réinitialisation de la limite de débit (vérifiez l’en-tête X-RateLimit-Reset)
  • Implémentez une logique de nouvelle tentative avec backoff exponentiel

L’éditeur ne se charge pas

Symptôme : le conteneur est vide ou affiche un indicateur de chargement indéfiniment Causes possibles :
  • Le script n’est pas chargé (vérifiez l’événement script.onload)
  • JWT ou identifiant de demande de signature invalide
  • Problèmes CORS (vérifiez la console du navigateur)
Solution :
  • Vérifiez que le script est chargé depuis https://app.firma.dev/embed/signing-request-editor.js
  • Vérifiez que le JWT est valide et non expiré
  • Assurez-vous que le backend renvoie les en-têtes CORS corrects

Étapes suivantes