Μετάβαση στο περιεχόμενο
Sursa Developers

Λήψη 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_, αποκωδικοποιημένο από Base64v1, + 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-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp και t1791194400
Σώμα{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}}
webhook-signaturev1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg=
X-KB-Signaturet=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 ημέρες. Κατά την επικάλυψη, κάθε παράδοση υπογράφεται και με τα δύο μυστικά.

  1. Κάντε την εναλλαγή και αποθηκεύστε το νέο μυστικό δίπλα στο παλιό στον παραλήπτη σας.
  2. Δέχεστε μια παράδοση αν επαληθεύεται με οποιοδήποτε από τα δύο μυστικά.
  3. Μετά την επικάλυψη, αφαιρέστε το παλιό μυστικό.

Αν το παλιό μυστικό έχει διαρρεύσει, τερματίστε αμέσως την επικάλυψη με το POST /v1/webhooks/{webhook_id}/revoke-previous-secret ή κάντε την εναλλαγή με "overlap_hours": 0.

Λίστα ελέγχου

  • Επαληθεύστε μία από τις υπογραφές πάνω στο ακατέργαστο σώμα και ελέγξτε τη χρονική σήμανση.
  • Αποθηκεύστε το συμβάν με κλειδί το webhook-id και μετά απαντήστε 2xx μέσα σε 10 δευτερόλεπτα.
  • Παραλείπετε τα διπλότυπα και τα ξεπερασμένα συμβάντα· διαβάζετε το τρέχον αντικείμενο μέσω του API όταν έχει σημασία.
  • Απαντάτε 429 ή 503 με Retry-After όταν θέλετε η Sursa να περιμένει· μην απαντάτε ποτέ 410 κατά λάθος.
  • Παραλείπετε τους τύπους συμβάντων και τα πεδία που δεν γνωρίζετε.
  • Κάνετε συμφωνία με την τροφοδοσία συμβάντων σε τακτά διαστήματα.