Webhook
Eventi dei documenti generati e non riusciti e dei template pubblicati, firmati con HMAC-SHA256 e ritentati.
I webhook informano i tuoi sistemi di ciò che accade in un workspace senza bisogno di polling: un documento generato tramite API, una generazione non riuscita, la pubblicazione di una versione di un template. Prynt invia una POST HTTPS con un corpo JSON al tuo endpoint, la firma e ritenta finché l'endpoint non risponde 2xx.
Come funzionano i webhook
- Aggiungi un endpoint in Sviluppatori → Webhook (permesso
webhooks.manage): un URLhttps://su un host pubblico, gli eventi da ricevere e una descrizione facoltativa. Prynt rifiuta gli indirizzi di reti private e localhost. - Prynt mostra una sola volta il segreto di firma dell'endpoint (
whsec_…). Conservalo insieme alla configurazione dell'endpoint; Ruota il segreto ne crea uno nuovo quando serve (quello vecchio smette subito di firmare). - Ogni consegna viene registrata con i tentativi, lo stato HTTP e un estratto della tua risposta. Invia evento di test invia un evento
webhook.testper verificare l'endpoint.
Eventi
| Campo | Tipo | Descrizione |
|---|---|---|
document.generated | event | Un documento è stato generato tramite l'API pubblica (in modo sincrono o asincrono). |
document.failed | event | Una generazione tramite l'API pubblica non è riuscita: errorCode e message spiegano il motivo. |
template.published | event | È stata pubblicata una versione di un template nel workspace. |
webhook.test | event | Inviato da Invia evento di test; viene sempre consegnato, qualunque siano gli eventi dell'endpoint. |
Payload
Ogni evento ha la stessa struttura: id (l'id dell'evento, un UUID), type, createdAt, workspaceId e i data dell'evento.
{ "id": "0199c2f2-0b6e-7f4c-a1d2-6c8e9b0a7f31", "type": "document.generated", "createdAt": "2026-10-08T09:12:04.051Z", "workspaceId": "0199b8a1-0000-7000-8000-00000000abcd", "data": { "template": "INVOICE", "version": 7, "environment": "production", "format": "pdf", "pageCount": 2, "outputBytes": 48213, "fileName": "INVOICE_18425.pdf", "runId": "0199c2f1-5a7e-7b31-9c4d-2f0a8e6b1d23", "jobId": "0199c2f1-7c10-7d2e-8f61-1b9a4c3e5d77", "documentUrl": "https://api.prynt.it/api/v1/documents/eyJhbGciOi…", "documentExpiresAt": "2026-10-09T09:12:04.020Z", "correlationId": "4bf92f3577b34da6a3ce929d0e0e4736", "apiKey": { "id": "0199b9c0-…", "prefix": "prynt_live_k3x9a2mq", "name": "ERP production" } }}I dati di ogni evento
document.generated:template,version,environment,format,pageCount,outputBytes,fileName,runId,jobId,documentUrl(un link firmato di breve durata),documentExpiresAt,correlationIde l'apiKey(id, prefisso, nome) che l'ha richiesto.document.failed:template,version,environment,format,jobId,runId,errorCode,message,correlationIde l'apiKey.template.published:templateId,template,version,previousVersion,format,noteepublishedBy.
Header
POST /prynt/webhooks HTTP/1.1Content-Type: application/jsonUser-Agent: Prynt-Webhooks/1.0Prynt-Event: document.generatedPrynt-Event-Id: 0199c2f2-0b6e-7f4c-a1d2-6c8e9b0a7f31Prynt-Delivery: 0199c2f2-0c11-7a05-b3e4-9d1f2a6c8e40Prynt-Signature: t=1760000000, v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdPrynt-Event: il tipo di evento;Prynt-Event-Id: l'id dell'evento (lo stesso a ogni nuovo tentativo e riconsegna);Prynt-Delivery: questa consegna.Prynt-Signature:t=<secondi unix>, v1=<firma>.
Verificare le firme
v1 è l'HMAC-SHA256 esadecimale di {t}.{raw body}, calcolato con i byte UTF-8 dell'intero segreto (compreso il prefisso whsec_). Per verificare una consegna:
- Leggi il corpo grezzo esattamente come ricevuto, prima di qualsiasi parsing JSON (interpretarlo e serializzarlo di nuovo cambia i byte).
- Calcola l'HMAC di
t, un punto e il corpo grezzo, e confrontalo conv1in tempo costante. - Rifiuta le consegne il cui
tsi discosta di più di 5 minuti dal tuo orologio: una richiesta intercettata non può essere riprodotta in seguito.
1using System.Security.Cryptography;2using System.Text;3 4static bool VerifyPryntSignature(string header, string rawBody, string secret, TimeSpan tolerance)5{6 // header: "t=1760000000, v1=5257a869…"7 string? t = null, v1 = null;8 foreach (var part in header.Split(',', StringSplitOptions.TrimEntries))9 {10 if (part.StartsWith("t=")) t = part[2..];11 else if (part.StartsWith("v1=")) v1 = part[3..];12 }13 if (t is null || v1 is null || !long.TryParse(t, out var seconds)) return false;14 var age = DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeSeconds(seconds);15 if (age.Duration() > tolerance) return false;16 17 using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); // l'intera stringa "whsec_…"18 var expected = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{t}.{rawBody}"));19 byte[] received;20 try { received = Convert.FromHexString(v1); } catch (FormatException) { return false; }21 return CryptographicOperations.FixedTimeEquals(expected, received);22}23 24// ASP.NET Core: leggi il corpo grezzo prima di qualsiasi binding JSON.25// var rawBody = await new StreamReader(Request.Body).ReadToEndAsync();26// var ok = VerifyPryntSignature(Request.Headers["Prynt-Signature"]!, rawBody, secret, TimeSpan.FromMinutes(5));Verifica prima di fidarti del contenuto
400 a una consegna con una firma mancante o errata e non elaborarla. Durante la rotazione del segreto, accetta per un breve periodo sia il vecchio sia il nuovo.Nuovi tentativi e riconsegna
Una consegna riesce quando il tuo endpoint risponde 2xx entro pochi secondi. In caso contrario (uno stato di errore, un timeout, una connessione fallita) Prynt riprova dopo 1 minuto, 5 minuti, 30 minuti, 2 ore e 12 ore: fino a 6 tentativi. Dopo l'ultimo la consegna risulta abandoned.
- Il registro delle consegne di ogni webhook mostra lo stato (pending, succeeded, failed, abandoned), il tentativo, il tentativo successivo e la tua risposta.
- Consegna di nuovo invia di nuovo un evento, come una nuova consegna con lo stesso id evento: utile quando hai sistemato il tuo endpoint.
- I webhook disattivati non ricevono nulla; la riconsegna e gli eventi di test rispondono 409 WEBHOOK_DISABLED finché non li riattivi.
Buone pratiche
- Rispondi 2xx rapidamente, poi svolgi il lavoro in modo asincrono (una coda, un job in background). Le risposte lente contano come errori e vengono ritentate.
- Sii idempotente: lo stesso evento può arrivare più di una volta (nuovi tentativi, riconsegne). Memorizza l'
iddegli eventi elaborati e ignora le ripetizioni. - Gli eventi possono arrivare fuori ordine: basati su
createdAte sugli id di job ed esecuzioni, non sull'ordine di arrivo. - Scarica
documentUrlsubito dopo l'evento: il link ha una durata breve. Rileggi il job (GET /v1/jobs/{jobId}) per ottenere un link nuovo.