Signlift

Webhooks

Recevez les événements en temps réel et vérifiez les signatures HMAC.

À chaque événement notable (notification d'un signataire, OTP envoyé, signature acquise, demande complétée ou expirée), Signlift POSTe un payload JSON signé HMAC-SHA256 sur l'URL configurée pour votre application externe.

Configuration

L'URL de webhook est configurée par application externe dans le dashboard, au moment où vous créez votre clé API. Le webhook_secret est affiché une seule fois — stockez-le aux côtés de votre clé API.

Événements

Cinq types d'événements sont émis vers les webhooks aujourd'hui :

EventDéclencheur
signer.notifiedNotification d'invitation envoyée à un signataire.
signer.otp_sentOTP envoyé à un signataire (e-mail ou SMS).
signer.signedUn signataire a finalisé sa signature.
request.completedTous les signataires ont signé et les PDF signés sont prêts.
request.expiredLa demande a dépassé sa fenêtre de validité.

Headers

Chaque requête entrante porte trois headers Signlift :

HeaderContenu
X-Signlift-EventType d'événement (ex. request.completed).
X-Signlift-DeliveryIdentifiant unique de la tentative de livraison. Idempotence côté client.
X-Signlift-SignatureSignature HMAC-SHA256 du body, format sha256=<hex>.

Le Content-Type est toujours application/json.

Format du payload

{
  "event": "request.completed",
  "occurred_at": "2026-05-17T15:12:43Z",
  "signature_request": {
    "id": 42,
    "status": "completed",
    "mode": "sequential"
  },
  "signer": {
    "id": 1768,
    "first_name": "Jeanne",
    "last_name": "Dupont",
    "full_name": "Jeanne Dupont",
    "email": "jeanne@example.com",
    "status": "signed"
  }
}
  • event : type d'événement (identique au header X-Signlift-Event).
  • occurred_at : horodatage ISO 8601 UTC.
  • signature_request : toujours présent — id, status, mode.
  • signer : présent uniquement pour les événements liés à un signataire (signer.notified, signer.otp_sent, signer.signed).
  • documents et certificate_url : présents uniquement pour request.completed — les URLs de téléchargement des documents signés et du certificat (voir ci-dessous).

Les URLs de téléchargement de request.completed

Le payload de request.completed contient un tableau documents (une entrée par document, dans l'ordre de la demande) et un certificate_url unique au niveau de la demande :

{
  "event": "request.completed",
  "occurred_at": "2026-05-17T15:12:43Z",
  "signature_request": {
    "id": 42,
    "status": "completed",
    "mode": "sequential"
  },
  "certificate_url": "https://signlift-production.s3.eu-west-3.amazonaws.com/...",
  "certificate_expires_in": 900,
  "documents": [
    {
      "document_id": 7,
      "signed_url": "https://signlift-production.s3.eu-west-3.amazonaws.com/...",
      "expires_in": 900
    }
  ]
}
  • signed_url : URL présignée du PDF signé (document + page de certificat, signature PAdES incluse).
  • certificate_url : URL présignée du certificat de signature. Il est généré une fois par demande et couvre tous ses documents, d'où sa position à la racine du payload.
  • expires_in / certificate_expires_in : durée de validité des URLs, en secondes. Toutes les URLs d'un même payload sont éphémères — générées à l'envoi (retries compris) et jamais stockées côté Signlift.

Les autres détails d'une demande (signataires, statuts par document, etc.) ne sont pas inclus dans le payload — récupérez-les via GET /api/v1/signature_requests/:id.

Vérification HMAC

Le header X-Signlift-Signature contient la signature HMAC-SHA256 du body brut (avant tout parsing JSON), encodée en hexadécimal et préfixée par sha256=.

require "openssl"
require "active_support/security_utils"

def verify_signlift_webhook(raw_body, header, secret)
  expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, raw_body)
  ActiveSupport::SecurityUtils.secure_compare(expected, header.to_s)
end
import crypto from "node:crypto"

function verifySignliftWebhook(rawBody, header, secret) {
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
  const a = Buffer.from(expected)
  const b = Buffer.from(header || "")
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}
import hmac
import hashlib

def verify_signlift_webhook(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header or "")

Filtrer par adresse source

En production, Signlift émet ses webhooks depuis une adresse IP fixe :

18.200.207.175

Vous pouvez n'accepter que cette adresse sur votre endpoint. C'est une couche en complément de la vérification HMAC, jamais à sa place : la signature est ce qui prouve le contenu du payload, l'adresse ne dit que d'où il vient.

Le filtrage le plus efficace se fait au bord — load balancer, reverse proxy, WAF — plutôt que dans votre application : le trafic non sollicité n'atteint alors jamais votre backend, au lieu d'y être simplement rejeté.

Cette adresse est stable. Si elle devait changer, nous préviendrons les intégrations concernées au moins 30 jours à l'avance.

Retry policy

Si votre endpoint répond avec un code HTTP ≠ 2xx, est injoignable, ou met plus de 5 secondes à répondre, Signlift réessaie selon le calendrier suivant :

TentativeDélai depuis l'échec précédent
11 minute
25 minutes
325 minutes
42 heures
510 heures

Soit 5 retries au total, sur environ 13 h 30. Après 5 échecs, la livraison est marquée failed et n'est plus retentée automatiquement.

Fiabilité : prévoyez une réconciliation

La livraison des webhooks est du meilleur effort : après épuisement des retries (endpoint indisponible ~13 h 30) ou en cas d'incident interne lors de la préparation des documents, un événement peut ne jamais vous parvenir alors que la demande a bien changé d'état.

Ne faites pas des webhooks votre unique source de vérité : si vous attendez un request.completed qui n'arrive pas dans un délai raisonnable, interrogez GET /api/v1/signature_requests/:id — le champ status fait foi, et la réponse contient les download_urls régénérées. Un polling de réconciliation (par exemple toutes les 15 minutes sur vos demandes en attente) couvre tous les cas de perte.

Idempotence côté client

Votre handler doit être idempotent : un même événement peut être livré plusieurs fois (échec puis retry, double notification réseau, etc.).

Utilisez X-Signlift-Delivery comme clé de déduplication — chaque tentative porte un identifiant unique, persistez-le côté votre app.

delivery_id = request.headers["X-Signlift-Delivery"]
return head :ok if WebhookEvent.exists?(delivery_id: delivery_id)
WebhookEvent.create!(delivery_id: delivery_id, ...)

On this page