Webhooks
Au lieu d’interroger, vous pouvez demander à Accessful d’envoyer par POST un événement signé à votre serveur dès qu’une tâche change d’état.
Comment fonctionne l’enregistrement
Section intitulée « Comment fonctionne l’enregistrement »Il n’y a ni tableau de bord ni endpoint d’enregistrement. Vous attachez un callback à chaque téléversement, et vous choisissez vous-même le secret de signature — Accessful n’en émet aucun.
- Téléversement multipart — envoyez les champs de formulaire
webhookUrletsecret. - Téléversement par URL — envoyez les champs JSON
callbackUrlethmacSignature.
curl -X POST "https://api.accessful.de/api/v1/upload-service/pdf/upload" \ -H "X-API-Key: $ACCESSFUL_API_KEY" \ -F "files=@document.pdf" \ -F "webhookUrl=https://votre-app.example.com/hooks/accessful" \ -F "secret=$VOTRE_WEBHOOK_SECRET"Événements
Section intitulée « Événements »type d’événement | Se déclenche quand |
|---|---|
case.running | La tâche a commencé son traitement. Peut se déclencher plusieurs fois (une par itération). |
case.completed | Le résultat PDF/UA est prêt à être téléchargé. |
case.failed | Le traitement a échoué. |
case.canceled | Le cas a été annulé. |
case.quota_exceeded | Rejeté car le quota contractuel est épuisé. |
Les états queued et quota_pending ne sont pas livrés en tant que webhooks.
Le corps de la requête est au format JSON. Content-Type: application/json.
{ "id": "f1d2c3b4-0000-4a1e-8f3c-2d6b5a9e1c40", "type": "case.completed", "apiVersion": "2026-06-05", "occurredAt": "2026-06-05T12:34:56Z", "data": { "caseId": "7c2f1e4a-9b0d-4a1e-8f3c-2d6b5a9e1c40", "fileName": "document.pdf", "jobStatus": "completed" }}| Champ | Type | Notes |
|---|---|---|
id | UUID | ID de l’événement. Utilisez-le comme clé d’idempotence (voir ci-dessous). |
type | string | L’un des types d’événement ci-dessus. |
apiVersion | string | Version du contrat (2026-06-05). Épinglez-la pour détecter les changements. |
occurredAt | ISO-8601 | Date de création de l’événement ; stable d’une tentative à l’autre. |
data.caseId | UUID | Le cas concerné par cet événement. |
data.fileName | string | Le nom du fichier. |
data.jobStatus | string | Statut brut de la tâche, p. ex. completed, failed, canceled, quota_exceeded. |
Les champs nuls sont omis du JSON.
En-têtes de livraison
Section intitulée « En-têtes de livraison »| En-tête | Exemple | Objet |
|---|---|---|
X-Accessful-Signature | t=1749126896,v1=9f86d0… | Signature HMAC — à vérifier |
X-Accessful-Webhook-Timestamp | 1749126896 | Secondes Unix ; identique au t= ci-dessus |
X-Accessful-Event-Id | f1d2c3b4-… | Égale id ; clé d’idempotence |
X-Accessful-Event-Type | case.completed | Routage sans analyser le corps |
X-Accessful-Case-Id | 7c2f1e4a-… | L’ID du cas |
X-Accessful-Delivery-Attempt | 1 | Compteur de tentatives, à partir de 1 |
X-Signature | n4bQgY… | legacy HMAC base64 sur le corps uniquement |
Vérifier la signature
Section intitulée « Vérifier la signature »L’en-tête X-Accessful-Signature a la forme t=<unix>,v1=<hex>. Recalculez-la et comparez en
temps constant :
- Lisez
tetv1dans l’en-tête. - Calculez
HMAC-SHA256(secret, "<t>.<corps brut>")et encodez-le en hexadécimal. La chaîne signée est l’horodatage, un point littéral, puis le corps brut exact de la requête — vérifiez avant l’analyse JSON. - Comparez en temps constant avec
v1. - Éventuellement, rejetez si
tdate de plus de quelques minutes (protection contre le rejeu).
import crypto from 'node:crypto';
// `rawBody` doit correspondre aux octets exacts reçus (p. ex. express.raw()).function verify(rawBody, signatureHeader, secret) { const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('='))); const expected = crypto .createHmac('sha256', secret) .update(`${parts.t}.${rawBody}`) .digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 min return valid && fresh;}String[] kv = signatureHeader.split(","); // ["t=...", "v1=..."]long t = Long.parseLong(kv[0].substring(2));String v1 = kv[1].substring(3);
Mac mac = Mac.getInstance("HmacSHA256");mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));byte[] sig = mac.doFinal((t + "." + rawBody).getBytes(StandardCharsets.UTF_8));String expected = HexFormat.of().formatHex(sig);
boolean valid = MessageDigest.isEqual( expected.getBytes(StandardCharsets.UTF_8), v1.getBytes(StandardCharsets.UTF_8));Nouvelles tentatives et idempotence
Section intitulée « Nouvelles tentatives et idempotence »- Succès = toute réponse 2xx dans le délai imparti (5 s pour se connecter, 10 s pour répondre). Répondez vite et effectuez votre travail de manière asynchrone.
- Échec (non-2xx, délai dépassé ou erreur de connexion) est réessayé jusqu’à 10
tentatives avec un backoff exponentiel et du jitter — environ
10s → 30s → 1,5m → 4,5m → 13,5m → 40m → 2h, puis plafonné à 6h, moins jusqu’à 20 % de jitter. La fenêtre complète s’étend sur plusieurs heures. - Après 10 tentatives échouées, l’événement est abandonné. Contactez le support pour une nouvelle livraison.
- Idempotence : le même événement peut arriver plus d’une fois (p. ex. une nouvelle
tentative après que votre endpoint a déjà réussi). Dédupliquez sur
X-Accessful-Event-Id— il est stable à chaque tentative.