Pour les développeurs
Documentation API
L'API REST de Signeur permet à votre application de créer des demandes de signature, de suivre leur statut et de télécharger les PDF verrouillés. Cette documentation décrit les endpoints disponibles aujourd'hui.
https://signeur.euAuthentification
Tous les appels API requièrent un jeton Bearer dans l'en-tête Authorization. Les jetons sont hachés en SHA-256 et comparés au hash stocké — le texte brut n'est jamais persisté.
Exemple :
Authorization: Bearer sgn_live_xxx...Obtenez une clé API depuis l'administrateur du service. Le format de la clé est sgn_live_<random>.
Réponses d'erreur
Toutes les erreurs sont retournées en JSON avec les champs error et message. Les codes HTTP suivent les conventions.
{
"error": "invalid_signer_email",
"message": "Field 'signer_email' is missing or not a valid email."
}| Code | Signification |
|---|---|
400 | Bad request (validointi ei mene läpi) |
401 | Authorization-otsikko puuttuu tai Bearer-token virheellinen |
404 | Resurssia ei löytynyt tai se kuuluu toiselle organisaatiolle |
409 | Konflikti: pyyntö on jo allekirjoitettu / ei vielä allekirjoitettu |
410 | Resurssi on vanhentunut (linkki yli 30 päivää vanha) |
429 | Liian monta pyyntöä — odota Retry-After-otsikon ilmoittama aika |
500 | Palvelinvirhe (DB-, Storage- tai email-välitysvirhe) |
Limites de débit
La limitation par organisation n'est pas encore appliquée. Utilisez l'API de manière responsable — les abus peuvent entraîner la révocation de la clé API. Des plafonds basés sur le volume seront ajoutés ultérieurement.
Idempotence
Tous les endpoints POST acceptent un en-tête `Idempotency-Key` facultatif (toute chaîne unique, généralement un UUID). Quand vous renvoyez une requête avec la même clé, Signeur rejoue la réponse originale au lieu de créer un doublon. Les clés sont par organisation et conservées 24 heures.
POST /api/v1/signatures
Authorization: Bearer sgn_live_...
Idempotency-Key: 0d8a2c41-7e3f-4b9d-bc59-2c5fd8a91f4d
Content-Type: application/json
{ … }Si vous envoyez la même clé avec un corps de requête différent, Signeur renvoie 409 idempotency_key_conflict. Les réponses rejouées incluent l'en-tête `Idempotent-Replayed: true` pour les détecter côté client.
Endpoints
Créer une demande de signature
Crée une nouvelle demande de signature (1 à 20 signataires), stocke le PDF dans Storage et envoie un e-mail d'invitation à chaque signataire.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
title | string | oui | Titre de la demande (visible dans l'e-mail d'invitation). |
message | string | non | Message facultatif affiché à chaque signataire. |
signers[] | array | oui | Tableau de 1 à 20 objets signataires. Au moins un est requis. |
signers[].email | string | oui | Adresse e-mail du signataire. |
signers[].name | string | non | Nom du signataire (affiché sur la page d'audit). |
signers[].locale | string | non | Locale e-mail par signataire : 'en' | 'fi' | 'fr' | 'de'. Fallback vers la locale globale, puis 'en'. |
locale | string | non | Locale par défaut appliquée aux signataires qui ne spécifient pas la leur. Valeurs autorisées : 'en' | 'fi' | 'fr' | 'de'. |
document.filename | string | oui | Nom de fichier original du PDF. |
document.content_base64 | string | oui | Contenu PDF encodé en base64 (max 10 Mo décodé). |
metadata | object | non | Objet clé-valeur libre stocké tel quel sur la demande. |
Rétrocompatibilité : l'ancien corps mono-signataire { "signer_email", "signer_name" } est toujours accepté et traité comme un tableau signers à un élément. Les nouvelles intégrations devraient utiliser le tableau signers[].
Corps de la requête
curl -X POST https://signeur.eu/api/v1/signatures \
-H "Authorization: Bearer sgn_live_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Order Agreement #1234",
"message": "Please review and sign.",
"locale": "en",
"signers": [
{ "email": "buyer@example.com", "name": "Anna Buyer" },
{ "email": "seller@example.com", "name": "Bob Seller", "locale": "fi" }
],
"document": {
"filename": "contract.pdf",
"content_base64": "JVBERi0xLjQK..."
},
"metadata": { "deal_id": "D-42" }
}'Réponse
{
"id": "6dc18911-f66b-43d6-9810-8de0b07bab04",
"status": "sent",
"expires_at": "2026-06-13T10:51:51.813+00:00",
"signers": [
{
"id": "9c0aa5ec-…",
"email": "buyer@example.com",
"name": "Anna Buyer",
"access_token": "29ErQSUr8Ov…",
"sign_url": "https://signeur.eu/sign/29ErQSUr8Ov…",
"email_sent": true,
"email_error": null,
"locale": "en"
},
{
"id": "f12d8b3c-…",
"email": "seller@example.com",
"name": "Bob Seller",
"access_token": "kpqDAuVy9-…",
"sign_url": "https://signeur.eu/sign/kpqDAuVy9-…",
"email_sent": true,
"email_error": null,
"locale": "fi"
}
]
}Récupérer le statut d'une demande
Retourne l'état actuel de la demande, le tableau complet des signataires et la piste d'audit. À utiliser pour le polling de statut.
Corps de la requête
curl https://signeur.eu/api/v1/signatures/<id> \
-H "Authorization: Bearer sgn_live_..."Réponse
{
"id": "6dc18911-f66b-43d6-9810-8de0b07bab04",
"status": "signed",
"title": "Order Agreement #1234",
"created_at": "2026-05-14T10:51:51.813Z",
"sent_at": "2026-05-14T10:51:52.012Z",
"signed_at": "2026-05-14T10:52:24.374Z",
"cancelled_at": null,
"expires_at": "2026-06-13T10:51:51.813Z",
"is_expired": false,
"signers": [
{
"id": "9c0aa5ec-…",
"email": "buyer@example.com",
"name": "Anna Buyer",
"access_token": "29ErQSUr8Ov…",
"sign_url": "https://signeur.eu/sign/29ErQSUr8Ov…",
"status": "signed",
"sent_at": "2026-05-14T10:51:52.012Z",
"opened_at": "2026-05-14T10:52:00.481Z",
"signed_at": "2026-05-14T10:52:24.374Z",
"declined_at": null,
"decline_reason": null,
"locale": "en"
}
],
"document_hash": "e5132c2fe36dfa219ec600b009d16ef38d20bf0849f7b01ba5ca6e22fec63b56",
"audit_trail": { "events": [ … ] },
"metadata": { "deal_id": "D-42" }
}Télécharger le PDF signé
Retourne le PDF verrouillé en binaire. Retourne 409 si la demande n'est pas encore signée.
Corps de la requête
curl https://signeur.eu/api/v1/signatures/<id>/document \
-H "Authorization: Bearer sgn_live_..." \
-o signed.pdfContent-Type: application/pdf
Lister les demandes de signature
Retourne une liste paginée des demandes de l'organisation avec filtres optionnels.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
status | query | non | Filtrer par statut : pending | sent | opened | signed | cancelled | declined | expired | awaiting_stamp | stamp_failed. |
created_from | query | non | Ne retourner que les demandes créées à partir de ce timestamp ISO-8601. |
created_to | query | non | Ne retourner que les demandes créées avant ce timestamp ISO-8601. |
limit | query | non | Taille de la page, 1 à 100. Défaut 25. |
cursor | query | non | Curseur opaque retourné par le next_cursor de la réponse précédente. Ne construisez pas le vôtre. |
Corps de la requête
curl "https://signeur.eu/api/v1/signatures?status=sent&limit=10" \
-H "Authorization: Bearer sgn_live_..."Réponse
{
"data": [
{
"id": "6dc18911-f66b-43d6-9810-8de0b07bab04",
"status": "sent",
"title": "Order Agreement #1234",
"signer_count": 2,
"created_at": "2026-05-14T10:51:51.813Z",
"sent_at": "2026-05-14T10:51:52.012Z",
"signed_at": null,
"cancelled_at": null,
"expires_at": "2026-06-13T10:51:51.813Z",
"is_expired": false
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wNS0xNFQxMDo1MTo1MS44MTNaIiwiaSI6IjZkYzE4OTExLi4uIn0"
}Annuler une demande de signature
Annule une demande en attente, invalide tous les liens de signataires restants et déclenche le webhook signature_request.cancelled.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
reason | string | non | Raison facultative stockée dans la piste d'audit (max 500 caractères). |
Corps de la requête
curl -X POST https://signeur.eu/api/v1/signatures/<id>/cancel \
-H "Authorization: Bearer sgn_live_..." \
-H "Content-Type: application/json" \
-d '{ "reason": "Sent to wrong recipient" }'Réponse
{
"id": "6dc18911-f66b-43d6-9810-8de0b07bab04",
"status": "cancelled",
"cancelled_at": "2026-05-17T08:14:02.913Z"
}Effet : le statut devient 'cancelled', tous les liens de signataires non signés sont invalidés et le webhook signature_request.cancelled est déclenché. Le PDF original est conservé mais n'est plus téléchargeable via /document.
Lister les signataires d'une demande
Retourne tous les signataires d'une demande avec leur statut, horodatages et piste d'audit. À utiliser pour afficher la progression multi-signataires.
Corps de la requête
curl "https://signeur.eu/api/v1/signatures/<id>/signers" \
-H "Authorization: Bearer sgn_live_..."Réponse
{
"data": [
{
"id": "9c0aa5ec-…",
"email": "buyer@example.com",
"name": "Anna Buyer",
"name_as_invited": "Anna Buyer",
"name_as_signed": "Anna H. Buyer",
"access_token": "29ErQSUr8Ov…",
"sign_url": "https://signeur.eu/sign/29ErQSUr8Ov…",
"status": "signed",
"position": 0,
"sent_at": "2026-05-14T10:51:52.012Z",
"opened_at": "2026-05-14T10:52:00.481Z",
"signed_at": "2026-05-14T10:52:24.374Z",
"declined_at": null,
"decline_reason": null,
"invalidated_at": null,
"ip_address": "87.92.91.57",
"user_agent": "Mozilla/5.0 (Macintosh; …) Safari/605.1.15",
"locale": "en",
"representing_as_invited": null,
"representing_as_signed": null,
"representing_role": null,
"representing_entity_id": null,
"audit_trail": { "events": [ … ] }
},
{
"id": "f12d8b3c-…",
"email": "seller@example.com",
"status": "pending",
"position": 1,
"...": "..."
}
]
}Renvoyer l'invitation à un signataire
Envoie de nouveau l'e-mail d'invitation à un signataire avec un code d'accès fraîchement régénéré. L'ancien code cesse de fonctionner immédiatement.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
locale | string | non | Facultatif. Remplace la locale e-mail du signataire pour cet envoi et persiste le changement sur la ligne signataire. Autorisé : 'en' | 'fi' | 'fr' | 'de'. |
Corps de la requête
curl -X POST https://signeur.eu/api/v1/signatures/<id>/signers/<signer_id>/resend \
-H "Authorization: Bearer sgn_live_..." \
-H "Content-Type: application/json" \
-d '{ "locale": "fi" }'Réponse
{
"ok": true,
"signer_id": "9c0aa5ec-…",
"email_sent": true,
"email_error": null,
"access_code_rotated_at": "2026-05-17T10:14:33.872Z"
}Effet : un nouveau code d'accès à 6 chiffres est généré, stocké en hash bcrypt et envoyé au signataire par e-mail. Le code précédemment envoyé n'authentifie plus la porte d'accès. Les compteurs de tentatives et les blocages temporaires éventuels sont réinitialisés.
Télécharger le PDF de piste d'audit
Retourne un PDF autonome contenant l'enregistrement d'audit complet : métadonnées de la demande, détails des signataires avec IP et user agents, chronologie des événements et informations de signature PAdES quand la demande est scellée. Ce PDF est informationnel et N'EST PAS signé cryptographiquement — le document scellé lui-même est la preuve légale.
Corps de la requête
curl https://signeur.eu/api/v1/signatures/<id>/audit-trail.pdf \
-H "Authorization: Bearer sgn_live_..." \
-o audit-trail.pdfContent-Type: application/pdf · Le PDF est généré à la demande à partir de l'état actuel de la demande et de la piste d'audit. Disponible quel que soit le statut de la demande.
Lister les endpoints webhook
Retourne tous les endpoints webhook configurés pour votre organisation. Les secrets ne sont pas inclus dans la liste — ils ne sont affichés qu'une seule fois lors de la création.
Corps de la requête
curl https://signeur.eu/api/v1/webhook-endpoints \
-H "Authorization: Bearer sgn_live_..."Réponse
{
"data": [
{
"id": "ep_a1b2c3d4-…",
"url": "https://example.com/hooks/signeur",
"event_types": ["signature_request.completed", "signer.signed"],
"description": "Production sync",
"disabled_at": null,
"created_at": "2026-05-17T10:00:00.000Z",
"updated_at": "2026-05-17T10:00:00.000Z"
}
]
}Créer un endpoint webhook
Enregistre une nouvelle URL pour recevoir les événements webhook. La réponse inclut un `secret` fraîchement généré qui n'est affiché que cette unique fois et qui sert à vérifier les signatures HMAC des webhooks entrants.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
url | string | oui | URL de destination (doit utiliser https:// en production, http://localhost autorisé pour le développement). |
event_types | string[] | non | Tableau de types d'événements à recevoir. Tableau vide (ou omis) = recevoir tous les événements. |
description | string | non | Libellé lisible facultatif pour l'endpoint (max 200 caractères). |
Corps de la requête
curl -X POST https://signeur.eu/api/v1/webhook-endpoints \
-H "Authorization: Bearer sgn_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/signeur",
"event_types": ["signature_request.completed", "signer.signed"],
"description": "Production sync"
}'Réponse
{
"id": "ep_a1b2c3d4-…",
"url": "https://example.com/hooks/signeur",
"secret": "whsec_7f3a9c…",
"event_types": ["signature_request.completed", "signer.signed"],
"description": "Production sync",
"disabled_at": null,
"created_at": "2026-05-17T10:00:00.000Z",
"updated_at": "2026-05-17T10:00:00.000Z"
}Important : le secret n'est affiché QUE dans cette réponse. Signeur ne conserve pas de copie en clair. Si vous le perdez, supprimez cet endpoint et créez-en un nouveau.
Mettre à jour un endpoint webhook
Met à jour l'url, event_types, description ou état disabled d'un endpoint. Tous les champs du corps sont facultatifs — seuls ceux fournis sont modifiés.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
url | string | non | URL de destination (doit utiliser https:// en production, http://localhost autorisé pour le développement). |
event_types | string[] | non | Tableau de types d'événements à recevoir. Tableau vide (ou omis) = recevoir tous les événements. |
description | string | non | Libellé lisible facultatif pour l'endpoint (max 200 caractères). |
disabled | boolean | non | true = pause la livraison sans supprimer l'endpoint, false = reprendre. |
Supprimer un endpoint webhook
Supprime définitivement l'endpoint. La livraison d'événements s'arrête immédiatement. Le secret est irrécupérable après suppression.
curl -X DELETE https://signeur.eu/api/v1/webhook-endpoints/<id> \
-H "Authorization: Bearer sgn_live_..."Webhooks
Signeur pousse les événements en temps réel vers une ou plusieurs URLs configurées pour votre organisation chaque fois que l'état d'une demande de signature change. Chaque événement est signé en HMAC-SHA256 et livré en parallèle à tous les endpoints concordants. C'est plus efficace que de poller l'endpoint GET.
Événements pris en charge
signature_request.created | Une nouvelle demande de signature a été créée. Déclenché une fois par demande, inclut la liste complète des signataires. |
signature_request.sent | Le batch d'e-mails d'invitation a été tenté pour tous les signataires. Inclut le statut email_sent par signataire. |
signature_request.opened | Un signataire a ouvert le lien pour la première fois. Déclenché une fois par demande quand le premier signataire l'ouvre. |
signer.signed | Un signataire a vérifié son OTP avec succès et signé. Déclenché N fois pour une demande à N signataires. |
signature_request.completed | Tous les signataires ont signé et le document est scellé. Inclut document_hash et download_url. |
signature_request.signed | Alias obsolète pour signature_request.completed — déclenché au même moment. Utilisez signature_request.completed pour les nouvelles intégrations. |
signature_request.declined | Un signataire a refusé ; la demande est passée au statut 'declined' et les autres signataires ont été invalidés. |
signature_request.cancelled | La demande a été annulée via le tableau de bord ou POST /signatures/{id}/cancel. |
signature_request.expired | La demande a atteint expires_at avant que tous les signataires aient signé. Envoyé une fois par demande lorsque le cron quotidien fait passer le statut à 'expired'. |
Format de la charge utile
// signer.signed — fires once per signer that finishes signing
{
"id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "signer.signed",
"created_at": "2026-05-14T10:52:24.374Z",
"data": {
"signature_request_id": "6dc18911-f66b-43d6-9810-8de0b07bab04",
"signer_id": "9c0aa5ec-…",
"signer_email": "buyer@example.com",
"signer_name": "Anna Buyer",
"signed_at": "2026-05-14T10:52:24.374Z",
"remaining_pending_signers": 1
}
}
// signature_request.completed — fires once when all signers are done
// and the PDF is sealed. signature_request.signed is a deprecated alias
// that fires at the same moment with the same payload.
{
"id": "evt_b2c3d4e5-f6a7-8901-bcde-f23456789012",
"type": "signature_request.completed",
"created_at": "2026-05-14T10:55:11.812Z",
"data": {
"signature_request_id": "6dc18911-f66b-43d6-9810-8de0b07bab04",
"status": "signed",
"signed_at": "2026-05-14T10:55:11.812Z",
"document_hash": "e5132c2fe36dfa…",
"download_url": "https://signeur.eu/api/v1/signatures/6dc18911-…/document"
}
}Vérification de signature
Chaque webhook est signé en HMAC-SHA256 dans l'en-tête X-Signeur-Signature. Vérifiez-le pour vous assurer que la requête provient bien de Signeur.
Format de l'en-tête :
X-Signeur-Signature: t=1747212744,v1=abc123def456...Données signées :
<timestamp>.<raw json body>Exemple de vérification (Node.js) :
import { createHmac } from "crypto";
// Verify webhook signature
const sig = req.headers["x-signeur-signature"];
const [t, v1] = sig.split(",").map(s => s.split("=")[1]);
const expected = createHmac("sha256", YOUR_WEBHOOK_SECRET)
.update(`${t}.${rawBody}`).digest("hex");
if (v1 !== expected) throw new Error("Invalid signature");Configuration
Configurez les endpoints de deux façons : (a) Tableau de bord → Webhooks offre une interface pour ajouter, modifier, suspendre ou supprimer des endpoints et choisir quels types d'événements chacun reçoit. (b) L'API REST /api/v1/webhook-endpoints permet de les gérer par programmation. Chaque endpoint reçoit son propre secret HMAC affiché une seule fois lors de la création — conservez-le en lieu sûr. Un webhook hérité à URL unique sur l'organisation est toujours honoré comme repli quand aucun endpoint n'existe dans la nouvelle table.
Livraison
Quand un événement se déclenche, Signeur recherche tous les endpoints actifs qui s'abonnent au type d'événement et livre le payload en parallèle (délai de 5 secondes par endpoint). Chaque tentative est consignée séparément dans la piste d'audit de la demande de signature avec l'id de l'endpoint, le statut HTTP et l'erreur éventuelle. Une file de re-tentatives est sur la feuille de route.
Bientôt disponible
Les fonctionnalités suivantes sont prévues :
- → Détection automatique d'expiration déclenchant signature_request.expired (actuellement le statut reste 'sent'/'opened' même après expires_at)
- → File d'attente de re-tentatives webhook — actuellement chaque livraison est une tentative unique ; les événements échoués ne sont pas retentés
- → Support d'Idempotency-Key sur les endpoints cancel et resend (actuellement uniquement POST /signatures)
- → Vue de l'historique de livraison des webhooks dans le tableau de bord