Για προγραμματιστές
Webhooks και συμβάντα
Τα webhooks θα στέλνουν όσα συμβαίνουν στον χώρο εργασίας σας στα δικά σας συστήματα. Δεν παραδίδονται ακόμη: αυτή η σελίδα δηλώνει τι υπάρχει σήμερα και τι έρχεται.
Οι διαδρομές, τα ονόματα πεδίων, οι τιμές enum, οι κεφαλίδες και οι κωδικοί σφαλμάτων στα παραδείγματα είναι οι πραγματικοί, ελεγμένοι με την προδιαγραφή του API κατά τη δημιουργία της σελίδας. Τα αναγνωριστικά, ποσά και ονόματα είναι ενδεικτικά. Τα `$WETALK_API_KEY` και `$AGENT` είναι μεταβλητές shell που ορίζετε εσείς.
Πού βρίσκονται σήμερα τα webhooks
Τα webhooks δεν παραδίδονται ακόμη. Κάθε παράδοση θα υπογράφεται ώστε το σύστημά σας να ελέγχει ότι προήλθε από το WeTalk και τα μυστικά υπογραφής δεν εκδίδονται ακόμη — το WeTalk δεν στέλνει ποτέ ανυπόγραφη παράδοση, οπότε τίποτα δεν αποστέλλεται μέχρι να εκδοθούν. Οι υπογεγραμμένες παραδόσεις, με το μυστικό να εμφανίζεται μία φορά, έρχονται.
Προς το παρόν, ένας προορισμός προστίθεται μέσω API με κλειδί που μπορεί να αλλάζει βοηθούς (`agent:write`). Δεν υπάρχει ακόμη σελίδα προσθήκης στην κονσόλα· Ρυθμίσεις → Για προγραμματιστές εμφανίζει τους καταχωρισμένους προορισμούς. Κάθε προορισμός ανήκει σε χώρο εργασίας, ορίζει απόλυτο URL `https://` και εγγράφεται σε ένα ή περισσότερα είδη συμβάντων. Το μη κρυπτογραφημένο κείμενο απορρίπτεται: η υπογραφή αποδεικνύει ποιος έστειλε το σώμα, όχι ότι κανείς δεν το διάβασε.
curl https://api.wetalk.io/v1/webhook-endpoints \
-X POST \
-H "Authorization: Bearer $WETALK_API_KEY"
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0199f1c2-6b48-7d7a-b4bb-90712ca3e598" \
-d '{
"workspace_id": "0199f1c2-6b3f-7910-8c2d-5b0a4e9f1d66",
"url": "https://orders.romapizzeria.example/wetalk",
"event_kind": ["agent.published"]
}' Παραγγελίες και απαντήσεις ερευνών
Κανένα δεν απαιτεί webhook. Οι επιβεβαιωμένες παραγγελίες διατηρούνται στην καρτέλα Παραγγελίες του βοηθού και ένα πρόγραμμα τις διαβάζει μέσω API με κλειδί ανάγνωσης παραγγελιών (`order:read`). Οι απαντήσεις ερευνών διατηρούνται επίσης στην κονσόλα, στα αποτελέσματα κάθε έρευνας.
Είδη συμβάντων
Ο προορισμός εγγράφεται σε ένα ή περισσότερα από αυτά. Μέχρι στιγμής παράγεται μόνο `agent.published`· τα υπόλοιπα δεν παράγονται ακόμη.
- agent.published — ενεργοποιήθηκε νέα έκδοση βοηθού
- call.started, call.ended — τηλεφωνική συνομιλία ξεκίνησε ή τελείωσε
- thread.started, thread.ended — διαδικτυακή συνομιλία ή συνομιλία μηνυμάτων ξεκίνησε ή τελείωσε
- transcript.ready — η απομαγνητοφώνηση συνομιλίας είναι έτοιμη
- order.confirmed, booking.created — συμβάντα αποτελέσματος
- campaign.finished — ολοκληρώθηκε εξερχόμενη εκστρατεία
Τι θα περιλαμβάνει η παράδοση
Το σώμα θα έχει πάντα τα ίδια τέσσερα πεδία. Το `data` είναι το περιεχόμενο του συμβάντος· όλα τα υπόλοιπα αποτελούν το περίβλημα, το οποίο πρέπει να διαβάζετε. Δύο κεφαλίδες διευκόλυνσης θα περιέχουν αναγνωριστικό και είδος, ώστε ένας δρομολογητής να κατευθύνει χωρίς να αναλύσει πρώτα το σώμα.
POST /wetalk HTTP/1.1
Host: orders.romapizzeria.example
Content-Type: application/json
X-WeTalk-Signature: t=1789046400,v1=6b1f…
X-WeTalk-Event-Id: 0199f1c2-6b49-7e8b-85cc-a1823db4f6a9
X-WeTalk-Event-Kind: agent.published {
"event_id": "0199f1c2-6b49-7e8b-85cc-a1823db4f6a9",
"event_kind": "agent.published",
"created_at": "2026-09-14T19:20:00Z",
"data": {
"voice_agent_id": "0199f1c2-6b40-7a11-9d3e-6c1b5f0a2e77",
"agent_version_id": "0199f1c2-6b44-7c2e-a0d1-3e7f9b2c4a18",
"agent_version_label": "v3",
"previous_agent_version_id": "0199f1c2-6b42-7b91-8f40-2d6e8a1b3c07",
"action": "published",
"published_at": "2026-09-14T19:19:58Z"
}
} Επαλήθευση παράδοσης
Η κεφαλίδα θα διαχωρίζεται με κόμματα: ένα `t=` και ένα ή περισσότερα `v1=`. Η υπογεγραμμένη συμβολοσειρά είναι η χρονική σήμανση, μια τελεία και το ακατέργαστο σώμα ακριβώς όπως μεταδόθηκε — όχι επανασειριοποιημένο αντίγραφο, που μπορεί να διαφέρει στη σειρά κλειδιών ή στα κενά και να αποτύχει.
Η χρονική σήμανση περιλαμβάνεται στην υπογεγραμμένη συμβολοσειρά, ώστε να εφαρμόζεται το παράθυρο αποτροπής επανάληψης: απορρίψτε όσα έχουν διαφορά άνω των 300 δευτερολέπτων μεταξύ των ρολογιών μας. Αν το `t` ήταν εκτός υπογραφής, ένας επιτιθέμενος θα το άλλαζε απλώς.
Κατά την αντικατάσταση μυστικού η κεφαλίδα θα φέρει ένα `v1=` ανά μυστικό. Ελέγξτε κάθε `v1=` σε σταθερό χρόνο και σταματήστε στο πρώτο που ταιριάζει. Η αντικατάσταση μυστικού δεν είναι ακόμη διαθέσιμη· έρχεται μαζί με τα ίδια τα μυστικά.
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECOND = 300;
export function verifyWeTalkSignature(header, secret, rawBody) {
const part = header.split(",").map((one) => one.trim());
const stamp = part.find((one) => one.startsWith("t="))?.slice(2);
if (stamp === undefined || !/^[0-9]+$/.test(stamp)) return false;
const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(stamp));
if (skew > TOLERANCE_SECOND) return false;
const expected = createHmac("sha256", secret)
.update(`\${stamp}.\${rawBody}`, "utf8")
.digest("hex");
return part
.filter((one) => one.startsWith("v1="))
.some((one) => constantTimeEqual(one.slice(3), expected));
}
function constantTimeEqual(left, right) {
const a = Buffer.from(left, "utf8");
const b = Buffer.from(right, "utf8");
return a.length === b.length && timingSafeEqual(a, b);
} Η παράδοση ακριβώς μία φορά δεν προσφέρεται
Η παράδοση θα γίνεται τουλάχιστον μία φορά. Ένα `200` που δεν φτάνει ποτέ σε εμάς — η σύνδεση κλείνει μετά την καταχώρισή σας — δεν διακρίνεται από αποτυχία. Επαναλαμβάνουμε λοιπόν και βλέπετε το συμβάν δύο φορές. Η εναλλακτική, να το χαρακτηρίσουμε παραδομένο υπό αβεβαιότητα, χάνει συμβάντα. Η απώλεια συμβάντος είναι χειρότερη από τη διπλή επεξεργασία του.
Επομένως ο κανόνας είναι: **εξασφαλίστε idempotency βάσει `event_id`.** Είναι UUID v7, σταθερό σε κάθε επανάληψη του ίδιου συμβάντος και το μόνο κλειδί που χρειάζεστε.
Το αρχείο παραδόσεων
Κάθε προσπάθεια καταγράφεται, ειδικά όσες απέτυχαν. Αν ένας πελάτης πει «το webhook σας δεν ενεργοποιήθηκε ποτέ», η απάντηση δίνεται μόνο από αυτό το αρχείο. Καταγράφει λοιπόν την κατάσταση απάντησης που επιστρέψατε, απόσπασμα του σώματος, τη διάρκεια της προσπάθειας και τυχόν σφάλμα μεταφοράς.
Μέχρι να εκδοθούν μυστικά υπογραφής δεν αποστέλλεται τίποτα και το αρχείο το δηλώνει: κάθε προσπάθεια καταγράφεται χωρίς κατάσταση απάντησης και με το σφάλμα
Error: Cannot sign a webhook with no signing secret. Fail closed., και επαναλαμβάνεται όπως κάθε άλλη αποτυχία.
Είναι μία σελίδα εγγραφών ανά endpoint, από τη νεότερη, με σελιδοποίηση μέσω δείκτη όπως κάθε άλλη συλλογή. Η εγγραφή τεκμηριώνει όσα έφυγαν από την πλευρά μας, δεν τα ερμηνεύει.
curl -G https://api.wetalk.io/v1/webhook-endpoints/$ENDPOINT/attempts \
-H "Authorization: Bearer $WETALK_API_KEY" \
--data-urlencode "limit=50" | Πεδίο | Τι σημαίνει |
|---|---|
| attempt_index | 1 για την πρώτη παράδοση, μετά μία ανά επανάληψη. Είναι ο μετρητής προσπαθειών του αποστολέα, σταθερός μετά από επανεκκινήσεις, και δεν ξεκινά ξανά από το 1 για το ίδιο συμβάν. |
| requested_at | Πότε κάναμε το αίτημα, όχι πότε το αποφασίσαμε. |
| response_status | Τι απαντήσατε. Null όταν το αίτημα δεν παρήγαγε καθόλου απάντηση — άρνηση σύνδεσης, αποτυχία TLS ή δικό μας χρονικό όριο. |
| response_body_excerpt | Οι πρώτοι 2048 χαρακτήρες του σώματος απάντησής σας. Αρκούν για να βρείτε το ίχνος σφάλματος που επιστρέψατε, όχι για αντιγραφή των αρχείων σας. |
| duration_ms | Πόσο διήρκεσε η προσπάθεια, μαζί με χρονικό όριο που εξάντλησε και τα δέκα δευτερόλεπτα. |
| error | Η αποτυχία μεταφοράς, το χρονικό όριο ή η πρόταση που περιγράφει απάντηση εκτός 2xx. Null σε αποδεκτή παράδοση. |
| outbox_message_id | Το μήνυμα της ουράς από το οποίο στάλθηκε η προσπάθεια. Κάθε προσπάθεια για το ίδιο συμβάν το μοιράζεται, ώστε να ομαδοποιούνται οι επαναλήψεις. |
| webhook_endpoint_id | Σε ποιο endpoint σας κατευθύνθηκε. |
| Προσπάθεια | Ώρα αιτήματος | Κατάσταση | Διάρκεια | Αποτέλεσμα | Σφάλμα |
|---|---|---|---|---|---|
| 1 | 2026-09-14T19:20:00Z | 500 | 212 ms | Επαναλήφθηκε | the receiver answered 500 |
| 2 | 2026-09-14T19:20:30Z | — | 10000 ms | Επαναλήφθηκε | AbortError: This operation was aborted |
| 3 | 2026-09-14T19:21:30Z | 200 | 148 ms | Έγινε δεκτή | — |
Το πρόγραμμα επαναλήψεων
Μια παράδοση θεωρείται αποδεκτή με οποιοδήποτε 2xx. Οτιδήποτε άλλο — 4xx, 5xx, άρνηση σύνδεσης ή αίτημα χωρίς απάντηση μέσα σε 10 δευτερόλεπτα — είναι αποτυχία και το μήνυμα επιστρέφει στην ουρά.
Η αναμονή διπλασιάζεται από τα τριάντα δευτερόλεπτα και σταματά να αυξάνεται στις 6 ώρες. Το όριο έχει σημασία όσο και ο διπλασιασμός: χωρίς αυτό, ένα endpoint που επανέρχεται την τρίτη ημέρα δεν θα δοκιμαζόταν ξανά μέχρι την έκτη. Με το όριο επαναλαμβάνεται εντός έξι ωρών από την επαναφορά του, καλύπτοντας τη συνηθισμένη περίπτωση διακοπής για ένα απόγευμα.
Σκόπιμα δεν υπάρχει τυχαία διακύμανση. Αυτή κατανέμει επαναλήψεις που προγραμματίστηκαν όλες την ίδια στιγμή, ενώ εδώ καθεμία ορίστηκε όταν έγινε η συνομιλία που την παρήγαγε. Θα κόστιζε αναπαραγώγιμο αρχείο και το «γιατί η ένατη προσπάθεια έγινε στις 14:03» δεν πρέπει ποτέ να απαντάται με «τυχαία».
| Προσπάθεια | Αναμονή | Χρόνος εκτέλεσης |
|---|---|---|
| 2 | 30 δευτερόλεπτα | 30 δευτερόλεπτα μετά το συμβάν |
| 3 | 1 λεπτό | 1 λεπτό 30 δευτερόλεπτα μετά το συμβάν |
| 4 | 2 λεπτά | 3 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
| 5 | 4 λεπτά | 7 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
| 6 | 8 λεπτά | 15 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
| 7 | 16 λεπτά | 31 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
| 8 | 32 λεπτά | 1 ώρα 3 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
| 9 | 1 ώρα 4 λεπτά | 2 ώρες 7 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
| 10 | 2 ώρες 8 λεπτά | 4 ώρες 15 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
| 11 | 4 ώρες 16 λεπτά | 8 ώρες 31 λεπτά 30 δευτερόλεπτα μετά το συμβάν |
Οι επαναλήψεις σταματούν 7 ημέρες μετά το συμβάν, με αφετηρία το συμβάν και όχι την πρώτη προσπάθεια — ένα μήνυμα που περίμενε στην ουρά δεν κερδίζει επιπλέον εβδομάδα. Σε αυτό το παράθυρο ένα endpoint που δεν απαντά ποτέ δοκιμάζεται περίπου 37 φορές και μετά το μήνυμα εγκαταλείπεται αντί να επαναλαμβάνεται για πάντα. Οι προσπάθειες παραμένουν στο αρχείο.
Δύο ακόμη κανόνες. Το σώμα απάντησης διατηρείται μόνο στους πρώτους 2048 χαρακτήρες, αρκετούς για να βρείτε το ίχνος σφάλματος αλλά όχι για αντιγραφή των αρχείων σας. Αν διαγράψετε ή απενεργοποιήσετε endpoint με συμβάντα ακόμη στην ουρά, τα συμβάντα σταματούν — αλλά γράφεται εγγραφή προσπάθειας για καθένα που το εξηγεί, γιατί ένα αρχείο που σιωπά δεν εξηγεί τίποτα.
Το endpoint, οι παράμετροι και η μορφή απάντησής του περιγράφονται στην Αναφορά API, που δημιουργείται από την ίδια προδιαγραφή που παρέχει το API.