Partner Webhooks
Πώς να λαμβάνετε events της SmartphoneKey μέσω HTTP webhooks — ρύθμιση, αυθεντικοποίηση, παράδοση και συμπεριφορά retry.
Τα partner webhooks επιτρέπουν στο σύστημά σας να λαμβάνει ειδοποιήσεις σε πραγματικό χρόνο όταν συμβαίνουν events στη SmartphoneKey. Όταν ενεργοποιείται ένα event στο οποίο έχετε εγγραφεί, η SmartphoneKey κάνει ένα HTTP POST στο καταχωρημένο endpoint σας με το payload του event.
Πώς λειτουργεί
- Καταχωρείτε μια συνδρομή webhook με ένα URL προορισμού και (προαιρετικά) τους τύπους event που θέλετε να λαμβάνετε.
- Όταν ενεργοποιείται ένα event που ταιριάζει, η SmartphoneKey το παραδίδει στο endpoint σας ως HTTP POST.
- Το endpoint σας απαντά με κωδικό κατάστασης
2xxγια να επιβεβαιώσει τη λήψη. - Αν η παράδοση αποτύχει, το AWS EventBridge κάνει αυτόματα retry.
Στο παρασκήνιο, κάθε webhook υποστηρίζεται από έναν κανόνα EventBridge + API destination που έχει παρασχεθεί (provisioned) για το partnerId σας. Το EventBridge φιλτράρει events με βάση το detail.partnerId == <your orgId>, ώστε να βλέπετε πάντα μόνο events για τον δικό σας tenant.
Διαθέσιμοι τύποι event
Η SmartphoneKey δημοσιεύει events από δύο πηγές:
| Πηγή | Περιγραφή |
|---|---|
spk.api | Domain events από το B2C API (κλειδαριές, χρήστες, hubs, κάμερες, κλειδιά) |
spk.iot.shadow | Shadow updates του AWS IoT από συνδεδεμένες συσκευές hub |
Το detail-type κάθε event ταιριάζει με το όνομα του event σε PascalCase που παρατίθεται παρακάτω.
Lock events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
LockCreated | A new lock has been created in the system |
OwnerSet | A lock's owner has been assigned or changed |
OrganizationSet | A lock has been moved to a different organization |
KeyAdded | A physical key has been added to a lock |
KeyRemoved | A physical key has been removed from a lock |
ResidentAdded | A resident has been granted access to a lock |
ResidentRemoved | A resident's access to a lock has been revoked |
LockUuidChanged | A lock's UUID has been replaced |
LockOpenRequested | An unlock command was delivered to the lock |
LockOpenConfirmed | The lock reported that an unlock was carried out |
LockOpenUnconfirmed | An unlock was sent but the lock did not confirm the bolt moved, or did not answer in time |
KeyWriteRequested | A key write to physical NFC hardware was requested |
LockIvrCallCompleted | An IVR call to a resident about this lock ended (telephony-api). Delivered at least once — deduplicate on callId |
Temporary key events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
TempKeyCreated | Deprecated: a temporary key has been issued through a per-lock route |
TempKeyUsed | Deprecated: a temporary key has been used to unlock (see TempKeyDoorOpened) |
TempKeyDeleted | Deprecated: a temporary key has been deleted through a per-lock route |
TempKeyUpdated | Deprecated: a temporary key's rules changed through a per-lock route |
TempKeyIssued | A temporary key has been issued for one or more doors |
TempKeyChanged | A temporary key's validity, limit, access or guest instructions changed |
TempKeyRevoked | A temporary key has been revoked; none of its doors open with it any more |
TempKeyDoorOpened | A door has been opened with a temporary key |
User events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
UserCreated | A new user has been registered |
ProfileUpdated | A user's profile fields have changed |
UserPhoneNumberUpdated | A user's phone number has changed (only the masked number is carried) |
UserEmailUpdated | A user's email address has changed |
PhysicalKeyAdded | A physical key has been issued to a user |
PhysicalKeyRemoved | A physical key has been removed from a user |
WalletKeyPrepared | A wallet key (Apple/Google) is being provisioned |
WalletKeyCommitted | A wallet key has been issued and the pass is available |
WalletKeyRolledBack | A wallet key provisioning attempt was rolled back |
WalletKeyRemoved | A wallet key has been removed from a user |
WalletKeyOrgAssigned | An admin assigned a wallet key to its issuing organization |
AccessibleLockAdded | A user has been granted access to a lock |
AccessibleLockRemoved | A user has lost access to a lock |
UserSuspended | A user has been suspended |
UserReactivated | A previously suspended user has been reactivated |
WalletKeyAdded | A wallet key was added (legacy single-step path) |
LicensePlateAdded | A license plate has been registered to a user |
LicensePlateRemoved | A license plate has been removed from a user |
DeviceAdded | A device has been linked to a user |
DeviceRemoved | A device has been unlinked from a user |
DevicePushMutedUpdated | A user muted or unmuted alarm push notifications for one of their devices |
CallToOpenLockChosen | Locks or Matter devices were chosen to open when the user calls the call-to-open number, each on its own digit; replaces the previous choice whole; a preference, not an access grant |
CallToOpenLockCleared | The user's call-to-open choice was cleared whole by a ClearCallToOpenLock command; removing a chosen lock or device from the user's lists drops that target from the choice, without this event |
CallToOpenCallRecorded | A call from the user's own phone number was admitted to open the user's call-to-open choice (the lock open is requested next and may still fail; LockOpenRequested on the lock is the open itself); written only for an admitted call, never for a refused one, and without the phone number; an admitted call counts towards the user's call-to-open rate limit and repeated-call check even when its open then fails |
UserSubscriptionStatusUpdated | An organization's subscription status entry for a user changed (mobile app access); carries only the changed fields (delivered only to the organization whose entry changed) |
Hub events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
HubProvisioned | A hub has been provisioned (manufactured / known to the system) |
HubClaimed | A hub has been claimed by an organization and site |
HubReClaimed | A hub has been moved from one organization/site to another |
HubRoleSet | A hub's primary/secondary role has been set |
HubSuspended | A hub has been suspended |
HubDecommissioned | A hub has been decommissioned |
MigrationStarted | A hub-to-hub device migration has started |
DeviceMigrationStatusUpdated | A device's migration status has changed |
MigrationCompleted | A hub-to-hub device migration has finished |
Camera events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
CameraRegistered | A camera has been registered |
CameraUpdated | A camera's metadata or stream URLs have changed |
CameraDisabled | A camera has been disabled |
CameraDeleted | A camera has been deleted |
CameraLinkedToLock | A camera has been associated with a lock |
CameraUnlinkedFromLock | A camera has been disassociated from a lock |
CameraRoleCreated | A 3dEye access role has been created |
CameraRoleDeleted | A 3dEye access role has been deleted |
CameraRoleUserAssigned | A user has been added to a camera role |
CameraRoleUserRemoved | A user has been removed from a camera role |
CameraRoleCameraAssigned | A camera has been added to a role |
CameraRoleCameraRemoved | A camera has been removed from a role |
RTSP camera events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
RtspCameraRegistered | An RTSP camera has been registered; placement on a video worker follows |
RtspCameraUpdated | An RTSP camera's name or source has changed |
RtspCameraDeleted | An RTSP camera has been deleted |
RtspCameraAccessGranted | A user has been granted access to an RTSP camera |
RtspCameraAccessRevoked | A user's access to an RTSP camera has been revoked |
RtspCameraStreamStarted | An RTSP camera's stream has been started |
RtspCameraStreamStopped | An RTSP camera's stream has been stopped |
RtspCameraPlaced | An RTSP camera has been placed on a video worker and its stream confirmed |
RtspCameraPlacementFailed | An RTSP camera could not be placed on any video worker |
RtspCameraRecordingConfigured | An RTSP camera's recording retention was set (0 = recording off) |
RtspCameraRecordingsPurgeRequested | A purge of an RTSP camera's older recordings was requested |
RtspCameraRecordingsPurged | A requested recordings purge has finished |
RtspCameraRuntimeStatusChanged | An RTSP camera's runtime status changed, as reported by its video worker (delivered only for organization-owned cameras) |
RtspCameraMonitoringConfigured | An RTSP camera's availability monitoring was switched on or off |
RtspCameraUnavailabilityConfirmed | A monitored RTSP camera is still unavailable; repeated at most every five minutes while it stays so (delivered only for organization-owned cameras) |
Video worker events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
VideoWorkerStarted | A video worker (VMS box) started up; one event per organization with cameras on it, listing only that organization's cameras |
Matter device events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
MatterDeviceCreated | A Matter device has been registered |
ReportedShadowUpdated ¹ | A device reported new shadow state |
DesiredShadowUpdated ¹ | A new desired shadow state was pushed |
MatterPinSet | A PIN credential slot was provisioned on a Matter door lock (the PIN itself is never carried) |
MatterPinCleared | A PIN credential slot was cleared on a Matter door lock |
AppPinSendRequested | An app-PIN delivery attempt was sent to the lock |
AppPinShadowWriteFailed | An app-PIN write could not be made to the device shadow; the attempt will be retried |
AppPinAcked | The lock acknowledged an app-PIN write |
AppPinClearRequested | An app-PIN slot clear was sent to the lock |
AppPinGaveUp | The lock refused every app-PIN attempt; delivery stopped |
AppPinRedriven | App-PIN delivery was restarted from the beginning |
AppPinRetired | App-PIN delivery was dropped because the lock does not require a remote PIN |
AppPinDeliveryFailed | The bridge answered an app-PIN write without the lock carrying it out; the attempt will be retried |
MatterDeviceNameSet | The display name of a Matter device has been set |
¹ Δεν παραδίδεται σε webhooks. Μπορεί να οριστεί σε μια συνδρομή, αλλά δεν δημοσιεύεται ποτέ στο EventBridge — εγγραφείτε στο
Shadow Updateγια να παρακολουθείτε την κατάσταση shadow μιας συσκευής.
Alarm events (spk.api)
detail-type | Πότε ενεργοποιείται |
|---|---|
AlarmTriggered | A Matter sensor (smoke, CO, occupancy, …) reported an alarm condition |
AlarmCleared | A previously triggered alarm condition has cleared |
AlarmModeSet | Alarm pushes of a Matter device were switched on or off |
IoT events (spk.iot.shadow)
detail-type | Πότε ενεργοποιείται |
|---|---|
Shadow Update | An IoT hub device updated its AWS IoT Device Shadow |
Δείτε το Event Catalog για το payload κάθε event.
Payload Webhook
Το endpoint σας λαμβάνει ολόκληρο το event του EventBridge ως σώμα JSON. Το αντικείμενο detail περιέχει τα δεδομένα που είναι ειδικά για το event· για events spk.api το πραγματικό domain event βρίσκεται κάτω από το detail.data.
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"source": "spk.api",
"detail-type": "ResidentAdded",
"time": "2026-04-30T10:30:00Z",
"region": "eu-west-1",
"account": "123456789012",
"version": "0",
"resources": [],
"detail": {
"partnerId": "acme-corp",
"aggregateType": "lock",
"aggregateId": "550e8400-e29b-41d4-a716-446655440000",
"version": 7,
"timestamp": 1745922600123,
"data": {
"type": "ResidentAdded",
"lockId": 4321,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"userId": "9b3e1d5e-7c6a-4d4d-8b1a-2e4f6a8c0d1e",
"name": "Jane Resident",
"email": "resident@example.com",
"timestamp": "2026-04-30T10:30:00.123Z"
}
}
}
Τα πεδία που θα χρησιμοποιείτε πιο συχνά:
| Πεδίο | Σκοπός |
|---|---|
id | Το ID του φακέλου (envelope) EventBridge που δημιουργεί η AWS — χρησιμοποιήστε το ως κλειδί deduplication |
source | spk.api ή spk.iot.shadow |
detail-type | Το όνομα του event σε PascalCase (π.χ. ResidentAdded) |
detail.partnerId | Το orgId του tenant σας — έχει ήδη φιλτραριστεί από το EventBridge |
detail.aggregateType / aggregateId | Προσδιορίζει το aggregate που παρήγαγε το event |
detail.timestamp | Χρόνος προβολής (projection) σε epoch milliseconds (αριθμός) — διαφορετικός από το detail.data.timestamp, που είναι ο χρόνος ISO-8601 του domain event |
detail.data | Το πλήρες payload του event (δείτε το Event Catalog) |
Payload IoT Shadow Update
Το source είναι spk.iot.shadow και αρκετά πεδία του detail είναι stringified JSON — ο handler σας πρέπει να καλέσει JSON.parse() σε αυτά πριν προσπελάσει τις εμφωλευμένες τιμές.
{
"source": "spk.iot.shadow",
"detail-type": "Shadow Update",
"detail": {
"partnerId": "<tenant-org-id>",
"thingName": "<device-uuid>",
"reportedState": "<stringified JSON of reported state>",
"desiredState": "<stringified JSON of desired state>",
"deltaState": "{}",
"metadata": "<stringified IoT shadow metadata>",
"version": 42
}
}
Τα reportedState, desiredState, deltaState και metadata είναι stringified JSON ενσωματωμένα μέσα στο εξωτερικό αντικείμενο JSON. Κάντε parse κάθε πεδίο ξεχωριστά πριν προσπελάσετε το περιεχόμενό του.
Self-Service Εταίρων
Οι εταίροι μπορούν να διαχειρίζονται τα δικά τους διαπιστευτήρια και το webhook URL τους χωρίς να περνούν από την υποστήριξη της SmartphoneKey.
Partner Portal
Διατίθεται ένα web περιβάλλον ανά περιβάλλον (environment):
| Περιβάλλον | URL |
|---|---|
| Dev | https://partner-portal.spk-dev.workers.dev/ |
| Stage | https://partner-portal.spk-stage.workers.dev/ |
| Nonprod | https://partner-portal.spk-nonprod.workers.dev/ |
| Prod | https://partner-portal.spk-prod.workers.dev/ |
Οι εταίροι συνδέονται με Auth0 και μπορούν να:
- Δουν το
orgIdτους και το ενεργό API key τους (αποκαλύπτεται μία φορά κατά την πρώτη έκδοση, μασκαρισμένο στη συνέχεια) - Περιστρέψουν το API key τους — ανακαλεί ακαριαία το προηγούμενο
- Δημιουργήσουν ή ενημερώσουν το webhook URL τους
Δείτε τον οδηγό Partner Portal για μια ξενάγηση.
Ενημέρωση Webhook URL μέσω API
Αν προτιμάτε να το αυτοματοποιήσετε, μπορείτε να ενημερώσετε το webhook URL σας με το X-API-Key σας:
curl -X PATCH "https://admin-api.spk-dev.workers.dev/webhooks/$WEBHOOK_ID" \
-H "X-API-Key: $YOUR_PARTNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/webhooks/spk-v2"}'
Σημειώσεις:
- Μόνο το πεδίο
urlείναι τροποποιήσιμο μέσω αυτού του endpoint — για να αλλάξετε τη λίστα events ή να περιστρέψετε το secret, επικοινωνήστε με τον account manager σας. - Το HTTPS απαιτείται σε κάθε περιβάλλον για self-service ενημερώσεις εταίρων.
- Η κατάσταση του webhook επαναφέρεται σε
pendingμέχρι το EventBridge να επαληθεύσει ξανά το νέο URL.
Αυθεντικοποίηση Webhook
Ώστε το endpoint σας να μπορεί να επιβεβαιώνει ότι τα εισερχόμενα αιτήματα προέρχονται πραγματικά από τη SmartphoneKey, μια παράδοση μπορεί να φέρει ένα στατικό secret που η SmartphoneKey συμπεριλαμβάνει σε κάθε αίτημα. Αυτό ρυθμίζεται από την πλευρά του διαχειριστή στο EventBridge connection — επικοινωνήστε με τον account manager σας για να το ρυθμίσετε.
Υποστηριζόμενοι τύποι αυθεντικοποίησης
auth_type | Περιγραφή |
|---|---|
none | Δεν προστίθεται κεφαλίδα αυθεντικοποίησης. Αυτό είναι το προεπιλεγμένο για self-service webhooks. |
apiKeyHeader | Ένα στατικό secret αποστέλλεται σε μια προσαρμοσμένη κεφαλίδα αιτήματος της επιλογής σας. |
bearerToken | Ένα στατικό token αποστέλλεται στην κεφαλίδα Authorization: Bearer <token>. |
Η υπογραφή σώματος HMAC ανά αίτημα δεν είναι προς το παρόν διαθέσιμη. Δεν υπάρχει HMAC
X-SPK-Signatureτου σώματος του αιτήματος — μην χτίσετε επαλήθευση γύρω από κάτι τέτοιο. Τα webhooks που δημιουργούνται μέσω self-service έχουν προεπιλογήnone· για να επισυνάψει η SmartphoneKey μια στατική κεφαλίδα ή bearer token, ζητήστε από τον account manager σας να ρυθμίσειapiKeyHeaderήbearerToken.
Επαλήθευση ενός στατικού token
Όταν έχει ρυθμιστεί μια στατική κεφαλίδα ή bearer token, συγκρίνετε την τιμή που λάβατε με το αποθηκευμένο secret σας χρησιμοποιώντας σύγκριση σταθερού χρόνου (constant-time) για να αποφύγετε επιθέσεις χρονισμού (timing attacks):
import hmac
def verify_token(received: str, expected: str) -> bool:
return hmac.compare_digest(received.encode(), expected.encode())
import { timingSafeEqual } from 'crypto';
function verifyToken(received: string, expected: string): boolean {
const a = Buffer.from(received);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
Ως άμυνα σε βάθος (defense in depth), επιβεβαιώστε επίσης ότι το detail.partnerId ισούται με το orgId σας, και εξυπηρετείτε πάντα το webhook endpoint σας μόνο μέσω HTTPS.
Πολιτική παράδοσης και retry
Η SmartphoneKey παραδίδει webhooks μέσω AWS EventBridge API destinations, τα οποία κάνουν retry σύμφωνα με τη ρυθμισμένη πολιτική τους:
| Παράμετρος | Τιμή |
|---|---|
| Μοντέλο παράδοσης | At-least-once |
| Retries | Σύμφωνα με την πολιτική retry του EventBridge API destination (υπόκειται σε αλλαγές) |
| Backoff retry | Εκθετικό |
| Triggers retry | Αποκρίσεις 5xx και timeouts |
Επειδή η παράδοση είναι at-least-once, σχεδιάστε το endpoint σας να είναι idempotent — χρησιμοποιήστε το πεδίο id στον φάκελο EventBridge ως κλειδί deduplication. Τα events που εξαντλούν τα retries τους απορρίπτονται· επικοινωνήστε με την υποστήριξη αν χρειάζεστε ανάκτηση events που χάθηκαν.
Απαιτήσεις απόκρισης
Το endpoint σας πρέπει να:
- Επιστρέφει HTTP
2xxεντός του παραθύρου timeout για να επιβεβαιώσει τη λήψη. - Οι αποκρίσεις
4xxαντιμετωπίζονται ως μόνιμες αποτυχίες και δεν γίνονται retried. - Οι αποκρίσεις
5xxή τα timeouts ενεργοποιούν retry με εκθετικό backoff.
Η επιστροφή 2xx αμέσως και η ασύγχρονη επεξεργασία συνιστάται για webhooks που ενεργοποιούν βαριά εργασία.
Φιλτράρισμα events
Κάθε συνδρομή webhook φιλτράρεται αυτόματα σε events των οποίων το detail.partnerId ισούται με το orgId σας. Δεν χρειάζεται να κάνετε φιλτράρισμα ανά event για το tenancy.
Μπορείτε επιπλέον να φιλτράρετε με βάση το detail-type (όνομα event) κατά την εγγραφή. Εγγραφείτε μόνο στους τύπους event που χρειάζεται η ενσωμάτωσή σας — αυτό μειώνει τον θόρυβο και την περιττή κίνηση προς το endpoint σας.
Βέλτιστες πρακτικές ασφάλειας
- Επαληθεύστε το token όταν είναι ρυθμισμένο — Αν σας έχει ρυθμιστεί μια στατική κεφαλίδα ή bearer token, ελέγξτε το (constant-time) πριν την επεξεργασία, και επιβεβαιώστε ότι το
detail.partnerIdταιριάζει με τοorgIdσας. - Χρησιμοποιήστε HTTPS — Καταχωρείτε μόνο HTTPS endpoints. Το HTTP απορρίπτεται εκτός του περιβάλλοντος dev.
- Απαντήστε γρήγορα — Επιστρέψτε
2xxπριν κάνετε βαριά επεξεργασία. Βάλτε τα events σε ουρά για ασύγχρονη επεξεργασία αν χρειάζεται. - Να είστε idempotent — Το ίδιο event μπορεί να παραδοθεί περισσότερες από μία φορές. Χρησιμοποιήστε το
idαπό τον φάκελο για deduplication. - Περιστρέφετε τα secrets περιοδικά — Ενημερώνετε το webhook secret σας τακτικά και υποστηρίξτε ένα σύντομο παράθυρο επικάλυψης κατά την περιστροφή.
Αποσφαλμάτωση παραδόσεων
Αν το endpoint σας δεν λαμβάνει events:
- Επιβεβαιώστε ότι το endpoint σας επιστρέφει
2xxγια ένα δοκιμαστικό POST. - Ελέγξτε ότι η συνδρομή σας περιλαμβάνει τους σωστούς τύπους event — περάστε
events: ['*']για να λαμβάνετε κάθε event για τον tenant σας. - Επαληθεύστε ότι το URL του endpoint σας είναι προσβάσιμο από το δημόσιο internet (όχι localhost ή ιδιωτικό δίκτυο).
- Ελέγξτε τον έλεγχο auth/token σας (αν είναι ρυθμισμένος) — μια απόκριση
4xxαπό το endpoint σας αντιμετωπίζεται ως μόνιμη αποτυχία και δεν θα γίνει retried. Μόνο αποκρίσεις5xxκαι timeouts ενεργοποιούν retries. - Ελέγξτε αν τα events ενεργοποιούνται καθόλου χρησιμοποιώντας το Event Catalog για να επιβεβαιώσετε πότε θα έπρεπε να παράγονται τα events που περιμένετε.
Εγγραφή σε όλα τα events
Περάστε events: ['*'] κατά τη δημιουργία μιας συνδρομής webhook για να λαμβάνετε κάθε event που φέρει το partnerId σας. Το wildcard καταρρέει σε ένα κενό φίλτρο detail-type στον υποκείμενο κανόνα EventBridge — το EventBridge τότε παραδίδει τα πάντα για τον tenant σας και ο handler σας μπορεί να κάνει switch στο detail-type για να τα κατανείμει (fan out).
{
"url": "https://your-api.example.com/webhooks/spk",
"events": ["*"]
}
Για να περιορίσετε αργότερα, αντικαταστήστε το events με μια ρητή λίστα από detail-type σε PascalCase από τους πίνακες παραπάνω.
Σχήμα & Δημιουργία κώδικα (Code Generation)
Κατεβάστε το σχήμα OpenAPI για να σκαλώσετε (scaffold) τον webhook handler σας:
Λήψη: eventbridge-schemas.yaml
Δημιουργήστε έναν typed client:
# TypeScript
npx openapi-generator-cli generate -i eventbridge-schemas.yaml -g typescript-fetch -o ./generated
# Python
openapi-generator-cli generate -i eventbridge-schemas.yaml -g python -o ./generated
# Go
openapi-generator-cli generate -i eventbridge-schemas.yaml -g go -o ./generated
Το συνοδευτικό σχήμα προς το παρόν υστερεί σε σχέση με τον πλήρη κατάλογο events παραπάνω — η αναδημιουργία του από την πηγή b2c-api παρακολουθείται ξεχωριστά. Χρησιμοποιήστε τους πίνακες αυτής της σελίδας ως την έγκυρη (authoritative) λίστα των detail-type που εκπέμπονται στο μεταξύ.