Webhooks να λαμβάνετε τα γεγονότα υπογραφής σε πραγματικό χρόνο
Διαμορφώστε ένα HTTPS URL στο ταμπλό Certyneo σας και λάβετε ένα POST υπογεγραμμένο με HMAC-SHA256 μόλις ένα συμβάν συμβεί στους φακέλους σας: υπογραφή, άρνηση, λήξη. 11 υποστηριζόμενα συμβάντα, 5 προσπάθειες παράδοσης με εκθετική καθυστέρηση, κρυπτογραφική επαλήθευση σε 8 γραμμές κώδικα.
< 5s
Μέση διάρκεια παράδοσης μετά το συμβάν
5x
Συνολικές προσπάθειες παράδοσης, κατανεμημένες σε περίπου 1 ώρα 20 λεπτά
HMAC-SHA256
Αλγόριθμος υπογραφής κάθε αίτησης
Καταλόγος γεγονότων
Τα 12 συμβάντα παρακάτω καλύπτουν ολόκληρο τον κύκλο ζωής ενός φακέλου Certyneo. Ενεργοποιήστε αυτά που σας ενδιαφέρουν στο Ρυθμίσεις → Webhooks, αγνοήστε τα υπόλοιπα — η συνδρομή είναι λεπτή ανά συμβάν.
| Ειδικότερα: | Επανάληψη |
|---|---|
envelope.created | Δημιουργείται ένα φάκελο (μέσω UI, API ή πρότυπου) χρήσιμο για να συγχρονιστεί μια καταγραφή στο CRM από την δημιουργία. |
envelope.sent | Ο φάκελος αποστέλλεται στους υπογράφοντες (πρώτο email που αποστέλλεται). |
envelope.completed | Όλοι οι υπογράφοντες έχουν υπογράψει και το σφραγισμένο PDF eIDAS αποθηκεύεται. Το payload φέρει signedDocumentUrl, έναν προ-υπογεγραμμένο σύνδεσμο ισχύει για 7 ημέρες. ως εναλλακτική, GET /v1/envelopes/id/signed-document, και το αρχείο ελέγχου μέσω GET /v1/envelopes/id/audit-trail. |
envelope.declined | Ένας υπογράφων έχει αρνηθεί τον φάκελο. Η διεύθυνση του άρνηση είναι σε `data.declinedBy` και ο λόγος, εάν έχει εισαχθεί, σε `data.reason`. |
envelope.voided | Ο φάκελος ακυρώθηκε από τον εκδότη πριν από την πλήρη υπογραφή. |
envelope.expired | Η ημερομηνία λήξης του φακέλου έχει περάσει χωρίς πλήρη υπογραφή. Ερώτημα GET /v1/envelopes/id για να μάθετε ποιοι υπογράφοντες λείπουν. |
envelope.returned_to_sender | Ένας υπογράφων έχει επιστρέψει τον φάκελο στον εκδότη για διόρθωση, χωρίς να τον αρνηθεί. Ο λόγος είναι σε `data.reason` και ο συγγραφέας της επιστροφής σε `data.returnedBy`. |
envelope.resubmitted | Ο εκδότης έχει διορθώσει και στη συνέχεια ξαναστείλει έναν φάκελο που είχε προηγουμένως επιστραφεί. Σηματοδοτεί την ανάληψη του κύκλου υπογραφής. |
recipient.signed | Ένας ατομικός υπογράφων έχει υπογράψει (αλλά όχι απαραίτητα όλοι). Χρήσιμο για την παρακολούθηση της προόδου και την αλυσίδα της επόμενης φάσης ενός διαδοχικού workflow. Προειδοποίηση: το σφραγισμένο PDF δεν υπάρχει ακόμη σε αυτό το στάδιο, ακόμη και για τον τελευταίο υπογράφοντα — μια λήψη που ξεκινά εδώ επιστρέφει HTTP 409. Χρησιμοποιήστε envelope.completed για το έγγραφο. |
recipient.viewed | Ένας υπογράφων άνοιξε το σύνδεσμο χωρίς να υπογράψει ακόμα, χρήσιμο για στοχευμένες εμπορικές αναζωπυρώσεις. |
recipient.approved | Ένας εγκριτής έχει επικυρώσει τον φάκελο χωρίς να υπογράψει (workflow εσωτερικής επικύρωσης). Η διεύθυνση είναι σε `data.approvedBy`. |
recipient.bounced | Ο διακομιστής ηλεκτρονικής αλληλογραφίας ενός παραλήπτη απέρριψε οριστικά την πρόσκληση ή την επανεκκίνηση (ανύπαρκτη θυρίδα, νεκρό domain). Ο παραλήπτης αλλάζει κατάσταση σε BOUNCED και οι αυτόματες επανεκκινήσεις σταματούν. Η διεύθυνση βρίσκεται στο `data.recipientEmail`: διορθώστε την και στείλτε ξανά το φάκελο. |
recipient.signed δεν σημαίνει «έγγραφο διαθέσιμο»
Το recipient.signed εκδίδεται για ΚΑΘΕ υπογράφοντα, τη στιγμή που ολοκληρώνει την υπογραφή του — συμπεριλαμβανομένου του τελευταίου, πριν το σφραγισμένο PDF συναρμολογηθεί και αποθηκευθεί. Μια λήψη που ενεργοποιείται από αυτό το handler λαμβάνει επομένως πάντα ένα HTTP 409 « Signed document not available until the envelope is COMPLETED ». Δεν είναι σφάλμα: είναι ένα « δεν είναι ακόμη έτοιμο ». Εγγραφείτε στο envelope.completed για ανάκτηση του εγγράφου, και διατηρήστε το recipient.signed για παρακολούθηση της προόδου (ποιος υπέγραψε, και πότε).
Φόρμα του φορτίου
Όλες οι παραδόσεις μοιράζονται το ίδιο σχήμα JSON κορυφαίου επιπέδου: `event`, `data` και `timestamp`. Το όνομα του γεγονότος επαναλαμβάνεται επίσης στην κεφαλίδα `X-Certyneo-Event`, η οποία επιτρέπει τον δρομολόγηση ακόμη και πριν την ανάλυση του σώματος. Το περιεχόμενο του `data` ποικίλλει ανάλογα με το γεγονός, αλλά παραμένει πάντα ένα επίπεδο αντικείμενο απλών τιμών — ποτέ πίνακας ή ένθετο αντικείμενο. Ακολουθεί μια πλήρης παράδοση `envelope.completed`.
POST /webhooks/certyneo HTTP/1.1
Host: your.app
Content-Type: application/json
X-Certyneo-Event: envelope.completed
X-Certyneo-Delivery-Id: cm8f2h6a10004qr9k5p2wm3xt
X-Certyneo-Signature: 4f3d1c8b2a9e7f60d5c4b3a2918e7f6d5c4b3a2918e7f6d5c4b3a2918e7f6d5c{
"id": "cm8f2h6a10004qr9k5p2wm3xt",
"event": "envelope.completed",
"data": {
"envelopeId": "cm7x2k9p40001qz8h3f7bn2ld",
"sandbox": false,
"subject": "Contrat de prestation Acme Corp",
"status": "COMPLETED",
"signatureLevel": "ADVANCED",
"aesSignerCount": 2,
"completedAt": "2026-05-27T08:42:13.000Z",
"recipientCount": 2,
"recipients": [
{ "email": "alice@acme.com", "name": "Alice Martin", "role": "SIGNER", "status": "SIGNED" },
{ "email": "bob@acme.com", "name": "Bob Durand", "role": "SIGNER", "status": "SIGNED" }
],
"signedDocumentUrl": "https://storage.certyneo.com/signed/cm7x2k9p4.../contrat.pdf?X-Amz-Expires=604800&X-Amz-Signature=..."
},
"timestamp": "2026-05-27T08:42:13.521Z"
}Το signedDocumentUrl είναι ένας προ-υπογεγραμμένος σύνδεσμος που ισχύει 7 ημέρες από το γεγονός: αποφεύγει μια δεύτερη αυθεντικευμένη κλήση για ανάκτηση του PDF. Απουσιάζει — και όχι null — εάν η προ-υπογραφή απέτυχε ή σε έναν φάκελο QES που ολοκληρώθηκε χωρίς αποθηκευμένο έγγραφο. Στη συνέχεια, επιστρέψτε στο GET /v1/envelopes/id/signed-document, η οποία παραμένει η πηγή της αλήθειας. Προσοχή και στην χειροκίνητη αναπαραγωγή από την dead-letter queue περισσότερο από 7 ημέρες μετά το γεγονός: ο σύνδεσμος του φορτίου έληξε, το API endpoint όχι.
Το payload είναι κωδικοποιημένο σε UTF-8, χωρίς BOM. Η υπογραφή HMAC υπολογίζεται στο πρωτότυπο σώμα όπως αποστέλλεται μην αλλοιώνετε τα κενά, η επαναπαραγραφή JSON συχνά αλλάζει την σειρά των κλειδιών και σπάει την επαλήθευση.
Ελέγξτε την υπογραφή HMAC
Κάθε αίτημα υπογράφεται με το μυστικό σας webhook (εμφανίζεται μόνο μία φορά κατά τη δημιουργία της συνδρομής). Η υπογραφή μεταδίδεται στην κεφαλίδα `X-Certyneo-Signature`: είναι το HMAC-SHA256 του ακατέργαστου σώματος του αιτήματος, κωδικοποιημένο σε δεκαεξαδικό, χωρίς πρόθεμα ή χρονόσημο. Επαληθεύστε ΠΑΝΤΑ την υπογραφή πριν επεξεργαστείτε το payload — χωρίς αυτό το βήμα, οποιοσδήποτε μπορεί να πλαστογραφήσει ένα γεγονός και να καλέσει το endpoint σας.
Node.js / TypeScript
import crypto from "node:crypto";
export function verifyCertyneoSignature(
rawBody: string,
signatureHeader: string,
secret: string,
): boolean {
// X-Certyneo-Signature = HMAC-SHA256(secret, rawBody), hex-encoded.
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const received = Buffer.from(signatureHeader, "hex");
const computed = Buffer.from(expected, "hex");
// timingSafeEqual throws when the two buffers differ in length —
// a malformed header must return false, not crash the handler.
if (received.length !== computed.length) return false;
// timingSafeEqual to mitigate timing attacks.
return crypto.timingSafeEqual(computed, received);
}Python
import hashlib
import hmac
def verify_certyneo_signature(
raw_body: bytes, signature_header: str, secret: str
) -> bool:
"""Verify a Certyneo webhook signature.
`X-Certyneo-Signature` holds the hex-encoded HMAC-SHA256 of the
raw request body, computed with your webhook secret.
"""
expected = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header)Συχνά σφάλματα
Χρησιμοποιήστε μια λειτουργία timing-safe (`crypto.timingSafeEqual` στο Node, `hmac.compare_digest` στο Python). Διαφορετικά, η διαφορά χρόνου σύγκρισης μεταξύ δύο υπογραφών αποκαλύπτει σταδιακά το μυστικό σε έναν ασθενή επιτιθέμενο (timing attack).
Πολιτική επαναπροσδιορισμού
Εάν το endpoint σας χρειάζεται πολύ χρόνο για να ανταποκριθεί, αρνείται τη σύνδεση ή επιστρέφει 5xx (ή 429), γίνεται νέα προσπάθεια σύμφωνα με εκθετική υποχώρηση: 5 προσπάθειες συνολικά, σε περίπου 1 ώρα 20 λεπτά. Μετά την πέμπτη, το συμβάν μεταβαίνει σε dead-letter queue — διαθέσιμο και αναπαραγόμενο από το dashboard σας, αλλά δεν γίνεται νέα αυτόματη προσπάθεια. Μια ρητή άρνηση (401, 403, 404, 410, 422...) δεν γίνεται ποτέ νέα προσπάθεια: η απάντηση δεν θα άλλαζε, το συμβάν μεταβαίνει απευθείας στη dead-letter queue.
| Προσπάθεια | Χρόνος πριν τη δοκιμαστική δράση | Χρόνος που έχει περάσει από το γεγονός |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 15 min | 21 min |
| #5 | + 1 h | 1 h 21 |
Οι αναφερόμενες καθυστερήσεις είναι ελάχιστες: η σάρωση επανάληψης ελέγχεται από ένα cron, επομένως μια προσπάθεια μπορεί να ξεκινήσει ελαφρώς μετά τη θεωρητική ώρα. Τα εγκαταλελειμμένα συμβάντα παρατίθενται στο Webhooks → Αποτυχίες, με κουμπί χειροκίνητης αναπαραγωγής και χωρίς περιορισμό διάρκειας διατήρησης. Ένα endpoint που έχει πέντε διαδοχικές οριστικές αποτυχίες — ή που ανταποκρίνεται με 404 / 410 — απενεργοποιείται αυτόματα και λαμβάνετε ειδοποίηση μέσω email: επανενεργοποιήστε το μόλις διορθωθεί, ο μετρητής ξεκινά από το μηδέν (οποιαδήποτε επιτυχής παράδοση το επανατοποθετεί επίσης στο μηδέν).
Δοκιμασία χωρίς να στείλετε πραγματικό φάκελο
Δημιουργήστε πρώτα μια συνδρομή — η απάντηση περιέχει το `secret` που απαιτείται για την επαλήθευση HMAC, εμφανίζεται μόνο μία φορά. Από τα Ρυθμίσεις → Webhooks, το κουμπί « Δοκιμή » στέλνει στη συνέχεια ένα υπογεγραμμένο POST ακριβώς όπως μια πραγματική παράδοση και σας εμφανίζει την ακατέργαστη απάντηση του διακομιστή σας: αυτός είναι ο ταχύτερος τρόπος για να επικυρώσετε το handler σας, τοπικά μέσω ngrok ή σε συνεχή ολοκλήρωση.
# Create a subscription — "secret" is returned once, store it now.
curl -X POST https://api.certyneo.com/v1/webhooks \
-H "Authorization: Bearer $CERTYNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your.app/webhooks/certyneo",
"events": ["envelope.completed", "recipient.signed"]
}'Η διεύθυνση URL πρέπει να είναι δημόσια προσβάσιμη σε HTTPS: μια ιδιωτική διεύθυνση ή localhost απορρίπτεται από την προστασία SSRF κατά τη δημιουργία της συνδρομής.
Οι συνδρομές είναι διαχωρισμένες ανά περιβάλλον, όπως και τα κλειδιά: ένα webhook δημιουργημένο με κλειδί sk_test_ λαμβάνει μόνο γεγονότα φακέλων δοκιμής, ένα webhook δημιουργημένο με κλειδί sk_live_ μόνο γεγονότα φακέλων παραγωγής. Ένας φάκελος δοκιμής δεν φτάνει ποτέ στη URL παραγωγής σας, και κάθε φορτίο πληροφοριών υποδεικνύει «sandbox» (true ή false).
6 πρακτικές που πρέπει να ακολουθήσετε
- Ελέγξτε την υπογραφή HMAC ΠΡΙΝ οποιαδήποτε ανάγνωση του σώματος χρησιμοποιήστε μια χρονική ασφαλή σύγκριση.
- Αποδιπλασιασμός στην κεφαλίδα `X-Certyneo-Delivery-Id` (ταυτόσημη με `id` στο σώμα) αποθηκεύοντας τα αναγνωριστικά που έχουν ήδη δει αποθηκευμένα — μια αναπαραγωγή μπορεί να παραδώσει ξανά ένα γεγονός που έχετε ήδη επεξεργαστεί εάν το 2xx σας χάθηκε, με το ίδιο αναγνωριστικό σε κάθε προσπάθεια.
- Απαντήστε HTTP 2xx σε 10 δευτερόλεπτα το πολύ, στη συνέχεια επεξεργαστείτε ασύγχρονα (queue). Πέρα από αυτό, η παράδοση κόβεται και μετράται ως αποτυχία.
- Εγγραφή του σώματος + πλήρης υπογραφή κατά το debug η επαλήθευση HMAC συχνά αποτυγχάνει σε ένα BOM ή ένα αόρατο λευκό χώρο.
- Παρακολούθηση της σελίδας Webhooks → Αποτυχίες: λαμβάνετε ειδοποίηση μέσω email εάν το endpoint σας απενεργοποιηθεί, αλλά όχι συμβάν για συμβάν — ένα συμβάν που εξαντλεί τις προσπάθειές του, είναι δική σας δουλειά να το αναπαράγετε.
- Φιλτράρετε τα γεγονότα κατά τη συνδρομή παρά στο handler σας, και παραμείνετε κάτω από το όριο των 5 συνδρομών ανά λογαριασμό.
Δεν είστε υποχρεωμένοι να φιλοξενήσετε ένα τελικό σημείο για να λάβετε αυτά τα γεγονότα: ο συνδέσμος τοποθετεί τη συνδρομή για εσάς και ξεκινά τη ροή απευθείας σε ένα γεγονός φακέλου. Δείτε Certyneo για Power Automate και Microsoft 365.
Για να πάμε πιο πέρα
Έτοιμοι να συνδέσετε τα συστήματά σας;
Τα webhooks και το REST API περιλαμβάνονται από το πλάνο Standard. Δημιουργήστε το λογαριασμό σας, δημιουργήστε ένα κλειδί `sk_test_` και συνδέστε το endpoint σας σε sandbox πριν μεταβείτε σε παραγωγή.