Λήψη webhooks
Ένα webhook ενημερώνει το σύστημά σας για μια αλλαγή στη Sursa τη στιγμή που συμβαίνει: μια κάρτα δημοσιεύεται,
επαληθεύεται ή αρχειοθετείται, μια ερώτηση έμεινε αναπάντητη. Η Sursa στέλνει ένα POST μέσω HTTPS με το συμβάν σε
μια διεύθυνση που καταχωρίζετε. Αυτός ο οδηγός δημιουργεί έναν παραλήπτη που λειτουργεί σωστά παρά τις επαναλήψεις, τις
αλλαγές σειράς και τα διπλότυπα.
Για το τι σημαίνει και τι περιέχει κάθε συμβάν, δείτε την επισκόπηση των webhooks και τον κατάλογο συμβάντων στην αναφορά API.
1. Καταχωρίστε ένα webhook
Ένας διαχειριστής καταχωρίζει webhooks στη Sursa στο Administration → Integrations → Webhooks and API (διαχείριση,
ενσωματώσεις, webhooks και API) ή με το POST /v1/webhooks και ένα κλειδί με τον
ρόλο admin και την εμβέλεια webhooks:manage:
curl -X POST https://api.sursa.com/v1/webhooks \
-H "Authorization: Bearer $SURSA_ADMIN_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/sursa",
"description": "Sync verified cards to the help center",
"events": ["card.published", "card.updated", "card.verified", "card.archived"]
}'Η απόκριση περιέχει το μυστικό υπογραφής στο secret, που ξεκινά με whsec_. Εμφανίζεται μία φορά. Αποθηκεύστε
το μαζί με τον παραλήπτη σας, για παράδειγμα σε μια μεταβλητή περιβάλλοντος SURSA_WEBHOOK_SECRET.
Η διεύθυνση πρέπει να χρησιμοποιεί https και να αντιστοιχεί σε δημόσιες διευθύνσεις IP: οι διευθύνσεις σε ιδιωτικά
δίκτυα, στο δίκτυο loopback και σε άλλα δεσμευμένα δίκτυα απορρίπτονται κατά την καταχώριση και σε κάθε παράδοση. Για
να αναπτύσσετε στον δικό σας υπολογιστή, εκθέστε τον τοπικό σας παραλήπτη μέσω μιας υπηρεσίας δημιουργίας σήραγγας
(tunnelling) που του δίνει μια δημόσια διεύθυνση HTTPS.
Στείλτε ένα δοκιμαστικό συμβάν οποιαδήποτε στιγμή με το POST /v1/webhooks/{webhook_id}/test.
Η απόκριση δείχνει τι απάντησε ο παραλήπτης σας.
2. Πώς είναι μια παράδοση
POST /sursa HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: evt_0123456789abcdef0123456789abcdef
webhook-timestamp: 1791194400
webhook-signature: v1,l1LLl/aEl4dQq7Geb383KbyNeZgs7VZpiw6iuoJgunI=
X-KB-Signature: t=1791194400,v1=82aa6b27378d89d7d880d17c3fe96268598443ae0e64d059c0c41e94e5d3ffbe
X-KB-Event-Id: evt_0123456789abcdef0123456789abcdef
X-KB-Event: card.verified
X-KB-Delivery: 99812
X-KB-Attempt: 1
X-KB-Sequence: 1042
{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"api_version":"1.60.0","sequence":1042,"object":{"type":"card","id":"c1"},"payload":"full","data":{"card":{"id":"c1"}},"test":false}
| Κεφαλίδα | Σημασία |
|---|---|
webhook-id | Το ID του συμβάντος. Ίδιο σε κάθε επανάληψη. Χρησιμοποιήστε το για να απορρίπτετε τα διπλότυπα. |
webhook-timestamp | Πότε υπογράφηκε αυτή η προσπάθεια, σε δευτερόλεπτα Unix. |
webhook-signature | Η υπογραφή στη μορφή Standard Webhooks. |
X-KB-Signature | Η ίδια παράδοση υπογεγραμμένη στην παλαιότερη μορφή της Sursa. |
X-KB-Event | Ο τύπος του συμβάντος. |
X-KB-Attempt | Ο αριθμός της προσπάθειας, από το 1. |
X-KB-Sequence | Η θέση αυτής της παράδοσης στη σειρά των παραδόσεων του webhook. |
Τα ονόματα των κεφαλίδων δεν κάνουν διάκριση πεζών-κεφαλαίων· το framework σας μπορεί να τα δείχνει ως
X-Kb-Signature ή x-kb-signature.
Το σώμα είναι το συμβάν. Το data του εξαρτάται από τον τύπο. Ένα webhook ρυθμισμένο στη μορφή σώματος thin λαμβάνει
το συμβάν χωρίς data· σε αυτή την περίπτωση διαβάστε το αντικείμενο μέσω του API με το δικό σας κλειδί.
3. Επαληθεύστε την υπογραφή
Ελέγχετε την υπογραφή κάθε παράδοσης πριν την εμπιστευτείτε. Και οι δύο μορφές υπογράφουν τα ακριβή byte του σώματος, γι’ αυτό διαβάστε το ακατέργαστο σώμα πριν από οποιαδήποτε ανάλυση του JSON: η εκ νέου σειριοποίηση του JSON αλλάζει τα byte και χαλά τον έλεγχο.
| Μορφή | Υπογεγραμμένη συμβολοσειρά | Κλειδί | Υπογραφή |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Το μέρος του μυστικού μετά το whsec_, αποκωδικοποιημένο από Base64 | v1, + Base64 του HMAC-SHA256 |
Παλαιότερη μορφή (X-KB-Signature) | <t>.<body> | Ολόκληρη η συμβολοσειρά του μυστικού, ως κείμενο | v1= + δεκαεξαδική μορφή του HMAC-SHA256 |
Αρκεί να επαληθεύσετε μία από τις δύο. Προτιμήστε την κεφαλίδα Standard Webhooks: ο ίδιος έλεγχος υπάρχει σε πολλές
έτοιμες βιβλιοθήκες. Κατά την εναλλαγή του μυστικού, κάθε κεφαλίδα φέρει μία υπογραφή για κάθε έγκυρο μυστικό
(χωρισμένες με κενό στο webhook-signature, με επαναλαμβανόμενο v1= στο X-KB-Signature). Μια παράδοση είναι
έγκυρη αν ταιριάζει οποιαδήποτε από αυτές.
Απορρίπτετε επίσης μια χρονική σήμανση που απέχει περισσότερο από 5 λεπτά από το ρολόι σας. Έτσι κανείς δεν μπορεί να επαναλάβει αργότερα μια καταγεγραμμένη παράδοση.
# Check a saved delivery by hand: body.json holds the raw body, the values come from its headers.
SECRET="whsec_…"; WEBHOOK_ID="evt_…"; TIMESTAMP="1791194400"
# Standard Webhooks: compare with the value after "v1," in webhook-signature
KEY=$(printf '%s' "${SECRET#whsec_}" | tr '_-' '/+')
while [ $(( ${#KEY} % 4 )) -ne 0 ]; do KEY="$KEY="; done
KEY_HEX=$(printf '%s' "$KEY" | openssl base64 -d -A | xxd -p | tr -d '\n')
{ printf '%s.%s.' "$WEBHOOK_ID" "$TIMESTAMP"; cat body.json; } \
| openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY_HEX" -binary | openssl base64 -A
# Earlier format: compare with the value after "v1=" in X-KB-Signature
{ printf '%s.' "$TIMESTAMP"; cat body.json; } | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1Δοκιμάστε τον κώδικα επαλήθευσης υπογραφής
Εκτελέστε τον κώδικά σας με αυτές τις τιμές πριν τον συνδέσετε με πραγματικές παραδόσεις. Με το ρολόι ρυθμισμένο στη χρονική σήμανση, και οι δύο έλεγχοι πρέπει να περνούν και πρέπει να αποτυγχάνουν όταν αλλάζει ένα byte του σώματος.
| Είσοδος | Τιμή |
|---|---|
| Μυστικό | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp και t | 1791194400 |
| Σώμα | {"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}} |
webhook-signature | v1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg= |
X-KB-Signature | t=1791194400,v1=ce26a19cb63cd3ae620217bf7688e1df1044fa7d59d9ae148d215f0292b768dc |
4. Απαντήστε γρήγορα, επεξεργαστείτε αργότερα
Η Sursa περιμένει την απάντησή σας έως 10 δευτερόλεπτα. Οποιαδήποτε κατάσταση 2xx σημαίνει ότι η παράδοση
πέτυχε· οτιδήποτε άλλο, ακόμη και μια ανακατεύθυνση, μια λήξη χρονικού ορίου ή μια κλειστή σύνδεση, σημαίνει ότι
απέτυχε και θα επαναληφθεί.
Γι’ αυτό ο παραλήπτης πρέπει μόνο να επαληθεύει, να καταγράφει το συμβάν και να απαντά. Την πραγματική επεξεργασία κάντε τη μετά, από τη δική σας ουρά.
// Node.js 20+, no framework. verifyStandard and verifyLegacy come from step 3.
import http from "node:http";
const SECRET = process.env.SURSA_WEBHOOK_SECRET;
http
.createServer(async (req, res) => {
const parts = [];
for await (const part of req) parts.push(part);
const body = Buffer.concat(parts); // the raw bytes, before any JSON parsing
const valid = req.headers["webhook-signature"]
? verifyStandard(SECRET, req.headers, body)
: verifyLegacy(SECRET, req.headers["x-kb-signature"], body);
if (!valid) {
res.writeHead(400).end();
return;
}
const event = JSON.parse(body);
await saveEventOnce(event); // insert into your table or queue, keyed by event.id; ignore a duplicate
res.writeHead(204).end();
})
.listen(3000);Απαντάτε 2xx μόνο αφού το συμβάν αποθηκευτεί με ασφάλεια. Αν απαντήσετε πρώτα και μετά η διεργασία σας καταρρεύσει,
το συμβάν χάνεται για εσάς, επειδή η Sursa το έχει ήδη μετρήσει ως παραδομένο.
5. Επαναλήψεις
Μια αποτυχημένη παράδοση επαναλαμβάνεται με αυξανόμενες παύσεις: μετά από περίπου 1 λεπτό, 5 λεπτά, 30 λεπτά, 2 ώρες, 5 ώρες, 10 ώρες, 24 ώρες και 24 ώρες – εννέα προσπάθειες σε σχεδόν τρεις ημέρες. Κάθε παύση διαφέρει έως 20%, ώστε οι επαναλήψεις από πολλά webhooks να μη φτάνουν την ίδια στιγμή.
- Μια απάντηση
429ή503μεRetry-Afterμεταθέτει την επόμενη προσπάθεια τουλάχιστον σε αυτόν τον χρόνο, έως 24 ώρες αργότερα. Χρησιμοποιήστε την όταν το σύστημά σας είναι υπερφορτωμένο ή σε συντήρηση. - Μια απάντηση
410 Goneαπενεργοποιεί το webhook: στείλτε τη μόνο όταν δεν θέλετε άλλες παραδόσεις σε αυτή τη διεύθυνση. - Μετά από 10 διαδοχικές αποτυχημένες προσπάθειες, το webhook τίθεται σε παύση και ενημερώνονται οι διαχειριστές
του οργανισμού. Τα συμβάντα συνεχίζουν να συσσωρεύονται και, όταν συνεχιστεί η λειτουργία του webhook, στο
περιβάλλον διαχείρισης ή με το
POST /v1/webhooks/{webhook_id}/resume, παραδίδονται με τη σειρά. - Μετά την τελευταία προσπάθεια, μια παράδοση επισημαίνεται ως
exhausted. Μπορείτε να τη στείλετε ξανά χειροκίνητα.
Οι παραδόσεις ενός webhook, με κάθε προσπάθεια, τον κωδικό της απάντησής σας και τον χρόνο της, εμφανίζονται στο
GET /v1/webhooks/{webhook_id}/deliveries και στο περιβάλλον
διαχείρισης. Το POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver
στέλνει ξανά μία παράδοση. Μια επαναποστολή έχει το ίδιο webhook-id.
6. Σειρά και διπλότυπα
- Σειρά. Οι παραδόσεις προς ένα webhook γίνονται αυστηρά η μία μετά την άλλη, με τη σειρά του
sequence. Όσο μια παράδοση επαναλαμβάνεται, οι επόμενες περιμένουν. Διαφορετικά webhooks δεν περιμένουν το ένα το άλλο. - Διπλότυπα. Το ίδιο συμβάν μπορεί να φτάσει περισσότερες από μία φορές, για παράδειγμα όταν η απάντησή σας χάθηκε
στην επιστροφή. Αποθηκεύετε το
webhook-id(είναι επίσης τοidστο σώμα) κάθε συμβάντος που επεξεργάζεστε και παραλείπετε ένα που έχετε ήδη δει. - Σκεφτείτε με όρους κατάστασης. Για αλλαγές στο ίδιο αντικείμενο, συγκρίνετε το
created_atή τοsequenceμε ό,τι έχετε αποθηκεύσει και αγνοείτε ένα συμβάν που είναι παλαιότερο από την κατάσταση που έχετε ήδη. Αν έχετε αμφιβολίες, διαβάστε το τρέχον αντικείμενο μέσω του API: το συμβάν σας λέει ότι κάτι άλλαξε, το API σας λέει τι είναι τώρα.
7. Κάντε συμφωνία με την τροφοδοσία συμβάντων
Κάθε συμβάν διατηρείται επίσης για 30 ημέρες στην τροφοδοσία συμβάντων, είτε κάποιο webhook ήταν εγγεγραμμένο σε
αυτό είτε όχι. Αν ο παραλήπτης σας ήταν εκτός λειτουργίας για περισσότερο από όσο καλύπτουν οι επαναλήψεις ή αν θέλετε
να ελέγξετε ότι δεν χάθηκε τίποτα, διαβάστε την τροφοδοσία με ένα κλειδί που έχει τον ρόλο admin και την εμβέλεια
webhooks:manage:
curl -G https://api.sursa.com/v1/webhook-events \
-H "Authorization: Bearer $SURSA_API_KEY" \
--data-urlencode "type=card.*" \
--data-urlencode "created_since=2026-10-20T00:00:00Z" \
--data-urlencode "limit=200"Ένα συμβάν στην τροφοδοσία έχει το ίδιο id και την ίδια μορφή όπως σε μια παράδοση, χωρίς τα πεδία παράδοσης
sequence, payload και test. Επειδή ο παραλήπτης σας παραλείπει τα συμβάντα που έχει ήδη δει, μπορείτε να
εκτελείτε τη συμφωνία όσο συχνά θέλετε, για παράδειγμα μία φορά την ώρα.
Το GET /v1/webhook-events/{event_id} διαβάζει ένα συμβάν με βάση το ID του.
8. Κάντε εναλλαγή του μυστικού
Το POST /v1/webhooks/{webhook_id}/rotate-secret εκδίδει ένα νέο μυστικό
και κρατά το προηγούμενο έγκυρο για μια περίοδο επικάλυψης, 24 ώρες από προεπιλογή και έως 7 ημέρες. Κατά την
επικάλυψη, κάθε παράδοση υπογράφεται και με τα δύο μυστικά.
- Κάντε την εναλλαγή και αποθηκεύστε το νέο μυστικό δίπλα στο παλιό στον παραλήπτη σας.
- Δέχεστε μια παράδοση αν επαληθεύεται με οποιοδήποτε από τα δύο μυστικά.
- Μετά την επικάλυψη, αφαιρέστε το παλιό μυστικό.
Αν το παλιό μυστικό έχει διαρρεύσει, τερματίστε αμέσως την επικάλυψη με το
POST /v1/webhooks/{webhook_id}/revoke-previous-secret ή κάντε
την εναλλαγή με "overlap_hours": 0.
Λίστα ελέγχου
- Επαληθεύστε μία από τις υπογραφές πάνω στο ακατέργαστο σώμα και ελέγξτε τη χρονική σήμανση.
- Αποθηκεύστε το συμβάν με κλειδί το
webhook-idκαι μετά απαντήστε2xxμέσα σε 10 δευτερόλεπτα. - Παραλείπετε τα διπλότυπα και τα ξεπερασμένα συμβάντα· διαβάζετε το τρέχον αντικείμενο μέσω του API όταν έχει σημασία.
- Απαντάτε
429ή503μεRetry-Afterόταν θέλετε η Sursa να περιμένει· μην απαντάτε ποτέ410κατά λάθος. - Παραλείπετε τους τύπους συμβάντων και τα πεδία που δεν γνωρίζετε.
- Κάνετε συμφωνία με την τροφοδοσία συμβάντων σε τακτά διαστήματα.