Déployer votre premier Webhook
1. Créer un endpoint pour vos Webhooks
Une fois que vous connaissez vos besoins, vous pouvez créer un endpoint qui servira de récepteur pour vos Webhooks. Nous enverrons les événements à cet endpoint en utilisant l'URL HTTP(S) que vous aurez fournie lors de la création de vos Webhooks. Un exemple simple d'endpoint en Ruby pourrait ressembler à ceci :
require 'sinatra'
require 'json'
post '/webhook' do
puts request.body.read
end
Cet exemple a été créé à l'aide de la bibliothèque Sinatra. Ici, nous créons une route /webhook
qui écoutera les requêtes HTTP POST
.
2. Traiter les événements reçus
L'endpoint créé précédemment doit pouvoir récupérer les événements envoyés, les traiter et renvoyer une réponse 2xx
.
Une fois les événements envoyés par Cryptr, nos services attendent une réponse. Si la réponse est une réponse 2xx, nous supposons que vous avez reçu l'événement et nous n'essaierons pas de l'envoyer à nouveau.
Si, en revanche, vous nous envoyez une réponse différente, nous essaierons de vous renvoyer les événements à certains intervalles de temps. Pour en savoir plus sur les différentes réponses que vous pouvez nous envoyer et leurs actions, vous pouvez consulter notre section Comment bien gérer vos webhooks qui traite de ce sujet.
{
"__domain__": "3-belges",
"__environment__": "sandbox",
"__type__": "Event",
"code": "dir_sync.user.update.success",
"data": {
"__type__": "DirectorySyncEvent",
"change": "update",
"directory_sync_id": "2cbd4ad8-1f3c-4ea0-84b6-691d4d469e57",
"provider": "okta",
"resource": {
"changes": {
"new_values": {
"address": {
"country": "FR",
"formatted": "12 rue des freres lumières\n68200 Mulhouse, France",
"locality": "Mulhouse",
"postal_code": "68200",
"region": "Alsace",
"street_address": "12 rue des freres lumières"
},
"phone_numbers": [
{
"default": false,
"phone_country_code": 33,
"phone_national_number": "345676543",
"primary": true,
"type": "mobile",
"value": "+33345676543"
},
{
"default": true,
"phone_country_code": 33,
"phone_national_number": "687435543",
"primary": true,
"type": "work",
"value": "+33687435543"
}
],
"profile": {
"family_name": "O'neil",
"given_name": "April",
"preferred_username": "April O'neil",
},
"updated_at": "2023-06-06T08:49:33"
},
"previous_values": {
"address": {
"country": null,
"formatted": "12 rue des freres lumières\nMulhouse",
"locality": "Mulhouse",
"postal_code": null,
"region": null,
"street_address": "12 rue des freres lumières"
},
"phone_numbers": [],
"profile": {
"family_name": null,
"given_name": "Aprils",
"preferred_username": "O'neil",
},
"updated_at": "2023-06-06T08:06:21"
}
},
"id": "b4702922-5868-4db8-bcd9-87ab40af50ed",
"type": "User"
}
},
"errors": null,
"params": {
"addresses": [
{
"country": "FR",
"locality": "Mulhouse",
"postalCode": "68200",
"region": "Alsace",
"streetAddress": "12 rue des freres lumières"
}
],
"phoneNumbers": [
{
"primary": true,
"type": "work",
"value": "+33345676543"
},
{
"primary": true,
"type": "mbile",
"value": "+33687435543"
}
],
"name": {
"familyName": "O'neil",
"givenName": "April"
},
"displayName": "April O'neil",
},
"issued_at": "2023-06-06T08:49:33.075265Z",
"webhook_id": "webhook_2QauvX3scYnpadurUTdjrd59QrE"
}
Réponse
Pour que nous recevions votre réponse dans les plus brefs délais, vous devez envoyer une réponse dès que possible. Vous devez d'abord traiter l'envoi de la réponse avant tout autre traitement.
Retries
Tous les codes 3xx
, 4xx
et 5xx
seront traités par Cryptr pour vous envoyer à nouveau un événement à différents intervalles que vous pouvez consulter dans notre section comment bien gérer un Webhook. Si nous ne recevons pas de réponse ou si nous ne la recevons pas à temps, nous supposerons que votre point de terminaison n'existe pas et nous n'enverrons pas l'événement à nouveau.
3. Créer un webhook de test
Pour utiliser les Webhooks, vous devez en créer un à l'aide de l'API ou de votre tableau de bord Cryptr. Voici la page Webhook où vous pouvez créer un Webhook rapidement :
Ce Webhook écoutera tous les événements de synchronisation de répertoire. Si vous le souhaitez, vous pouvez modifier les codes d'événements à écouter en changeant la valeur de la clé event_codes
(actuellement disponible uniquement par API). Pour obtenir une liste complète des event_codes
disponibles, vous pouvez consulter notre section Liste des événements.
4. Tester la bonne réception des événements
Vérifiez que votre Webhook fonctionne correctement. Les événements sont-ils bien reçus ?
Maintenant que votre Webhook est créé, vous pouvez tester un événement correspondant à un événement écouté par votre Webhook. Dans le cas d'un Webhook qui écoute les événements Directory Sync, vous pouvez par exemple tester l'événement dir_sync.user.provision.success
.
Pour ce faire, nous vous fournissons un point d'API pour déclencher l'envoi de faux événements. Pour en savoir plus, voir Comment tester un Webhook ou notre documentation API.
Si vous avez utilisé le même endpoint que dans notre exemple de l'étape 2, vous devriez voir le corps de la requête POST
que nous vous avons envoyée dans votre terminal.
5. Déployez vos points d'extrémité sur vos services
Si les tests sont concluants, déployez votre endpoint Webhook dans vos services de production.
Une fois la configuration testée, vous pouvez répéter les étapes précédentes dans votre environnement de production. Il est possible de créer plusieurs webhooks différents qui écoutent plusieurs événements différents, mais aussi de définir des endpoints cibles différents pour chacun de vos webhooks. Par exemple, un Webhook qui envoie des événements SCIM à un Endpoint /scim-webhook
et un autre qui envoie des événements SSO à un Endpoint /sso-webhook
.
Comment gérer correctement vos Webhooks
Il est important de bien gérer vos Webhooks. Pour cela, quelques conseils et informations peuvent vous être utiles.
Sélectionnez les événements que vous souhaitez recevoir
La première chose à faire est de sélectionner judicieusement les événements reçus. Réfléchissez bien aux événements qui vous importent vraiment. En effet, avoir un Webhooks qui gère tous les événements peut être tentant mais cela pourrait rapidement surcharger votre Endpoint et rendre la lecture compliquée. C'est pourquoi il est préférable de privilégier la qualité à la quantité en se limitant aux événements dont vous avez réellement besoin.
Par exemple, si j'ai besoin de savoir quand un utilisateur est créé par SCIM, je dois écouter l'événement dir_sync.user.provision.success
.
Les différents codes d'état que vous pouvez renvoyer
La première chose à faire lorsque vous recevez un événement est bien sûr de nous envoyer une réponse avec un code 2xx. Mais vous pouvez aussi utiliser d'autres codes qui seront pris en compte différemment chez Cryptr.
Codes 2xx
Tous les codes entre 199 et 300 non inclus sont des codes signifiant un envoi réussi. Cryptr interprétera alors ces réponses comme telles et supposera que vous avez reçu l'événement.
410 Code
Le code 410 est utilisé pour nous informer que vous souhaitez annuler l'envoi d'un événement. Si vous nous envoyez un code 410, nous cesserons de vous envoyer cet événement.
4xx codes
Pour tous les autres codes 4xx, Cryptr tentera de vous renvoyer l'événement à différents intervalles, comme suit :
Tentative initial | 0 |
---|---|
Tentative #1 | 5 minutes |
Tentative #2 | 10 minutes |
Tentative #3 | 20 minutes |
Tentative #4 | 1 hour |
Tentative #4 | 2 hours |
503 retry-after
Cette option, qui peut être incluse dans l'en-tête HTTP de la réponse que vous nous envoyez, vous permet de définir vous-même une durée après laquelle nous réessayerons de vous envoyer l'événement.
Pour ce faire, il suffit de nous envoyer un code de réponse 503 et d'inclure dans l'en-tête http de votre réponse HTTP la clé retry-after
suivie d'une valeur en secondes. En voici un exemple :
{“retry-after”, 60}
Cela redémarre l'envoi d'un événement à votre point de terminaison après 60 secondes.
Liste des événements
Si vous souhaitez recevoir tous les événements Directory Sync, il vous suffit d'indiquer dir_sync.all
dans la clé event_codes
au lieu de la liste des codes d'événements.
Codes des événements de synchronisation de l'annuaire
Événement | Event_Code |
---|---|
Directory Sync activé | dir_sync.activate.success |
Directory Sync désactivé | dir_sync.deactivate.success |
Utilisateur crée via Directory Sync | dir_sync.user.provision.success |
Utilisateur mis à jour via Directory Sync | dir_sync.user.update.success |
Utilisateur supprimé via Directory Sync | dir_sync.user.deprovision.success |
Utilisateur creation via Directory Sync échoué | dir_sync.user.provision.fail |
Utilisateur update via Directory Sync échoué | dir_sync.user.update.fail |
Utilisateur deletion via Directory Sync échoué | dir_sync.user.deprovision.fail |
Groupe crée via Directory Sync | dir_sync.group.provision.success |
Groupe mis à jour via Directory Sync | dir_sync.group.update.success |
Groupe supprimé via Directory Sync | dir_sync.group.deprovision.success |
Création d'un Groupe via Directory Sync échoué | dir_sync.group.provision.fail |
Mise à jour d'un Groupe via Directory Sync échoué | dir_sync.group.update.fail |
Suppression d'un Groupe via Directory Sync échoué | dir_sync.group.deprovision.fail |
Utilisateur activé via Directory Sync | dir_sync.user.activate.success |
Utilisateur désactivé via Directory Sync | dir_sync.user.deactivate.success |
Activation d'un Utilisateur via Directory Sync échoué | dir_sync.user.activate.fail |
Désactivation d'un Utilisateur via Directory Sync échoué | dir_sync.user.deactivate.fail |
Groupe activé via Directory Sync | dir_sync.group.activate.success |
Groupe désactivé via Directory Sync | dir_sync.group.deactivate.success |
Activation d'un Groupe via Directory Sync échoué | dir_sync.group.activate.fail |
Désactivation d'un Groupe via Directory Sync échoué | dir_sync.group.deactivate.fail |
Sécurité des Webhooks
Chaque événement envoyé par Cryptr à votre (vos) endpoint(s) contient une signature. La sécurité est un concept important, c'est pourquoi cette signature a été ajoutée. Vous pouvez l'ignorer si vous le souhaitez, mais il n'est pas recommandé de le faire. En effet, cette clé vous permettra de vérifier que les événements reçus ont bien été envoyés par Cryptr et non par une personne extérieure.
1. Obtenir les données nécessaires
Obtenir la clé de signature Cryptr
La signature Cryptr est située dans l'en-tête de la requête HTTP sous la clé cryptr-signature
. En effet, lorsque vous recevez une requête ou une réponse HTTP, le payload est constituée d'informations du protocole HTTP, telles que des en-têtes, une URL, le contenu du corps, la version et les informations d'état.
Voici à quoi ressemble un en-tête HTTP :
Content-Type: application/json; charset=UTF-8
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0
cryptr-signature: t=1676905124,v1=sha256.640e3f227fed2123e84101cc69f41fba3435ec3b8f28d4ea249231f928efa990,v0=sha256.3d5990d850a7bbe430bf6ed93329f5b61b3fb72871afed9a071f3b95317d73ab
Par exemple, vous pouvez récupérer la cryptr-signature
en exécutant la fonction suivante :
cryptrSignature = request.env["HTTP_CRYPTR_SIGNATURE"]
Voici un exemple de signature Cryptr :
cryptr-signature: v1=640e3f227fed2123e84101cc69f41fba3435ec3b8f28d4ea249231f928efa990,v0=3d5990d850a7bbe430bf6ed93329f5b61b3fb72871afed9a071f3b95317d73ab | ,
---|
Récupérer l'horodatage
Chaque cryptr-signature
contient un timestamp
. Cet horodatage, que vous devrez récupérer et utiliser, empêche les attaques par rejeu.
Vous pouvez procéder comme suit pour récupérer l'horodatage :
# Split the cryptr-signature
array = cryptrSignature.split
# Get the timestamp from the splitted signature
timestamp = array.first
Extraire la signature
Chaque cryptr-signature
contient également une signature v1
. Mais il peut aussi contenir une signature v0
si votre secret a été mis à jour.
La signature v1
est la signature la plus récente, la signature v0
est la signature réalisée avec la signature_key
précédente. Ceci a été pensé pour que vous ayez le temps de faire les changements nécessaires sans être bloqué si la signature_key
change. Bien que les deux versions soient utilisables, il est fortement recommandé de n'utiliser que v1
dans la mesure du possible.
Pour récupérer vos signatures, procédez comme suit :
# Split the cryptr-signature
array = cryptrSignature.split
# Get the Signature V1 from the splitted signature
signatureV1 = array[1]
# Get only the necessary data
signatureV1.slice! "sha256."
# OPTIONAL: Get the Signature V0 from splitted signature
# signatureV0 = array[2]
# Get only the necessary data
# signatureV0.slice! "sha256."
Notez que nous ne prenons pas la valeur brute de la signature, mais seulement ce qui vient après le sha256.
.
⚠️ N'oubliez pas le .
.
Une fois cela fait, nous pouvons passer à l'étape suivante.
2. Récupérez votre clé secrète
Pour vérifier vos signatures, vous devez récupérer la signature_key
des webhooks dont vous voulez garantir l'authenticité. Si vous ne vous en souvenez pas, vous pouvez toujours la récupérer via la requête suivante :
curl 'https://${cryptr_service_url}/api/v2/webhooks/${webhook_id}'
Vous obtiendrez le résultat suivant :
{
"__environment__": "production",
"__type__": "Webhook",
"active": true,
"event_codes": ["dir_sync.user.provision.success"],
"id": "41f555de-6dac-4f24-8d0a-dc3499497ef0",
"inserted_at": "2023-11-07T15:31:30",
"name": "SCIM Webhook",
"signature_key": "0Zrk1pQnc10hh5ZDecqQfMDKy0S2FfdWU7ZJQ40Mh2TgweRcXM5Um3b6P0aUkFqf",
"target_url": "https://cryptr.site/webhook",
"updated_at": "2023-11-07T15:31:30"
}
Chaque webhook que vous créez aura une signature_key
différente. Même si vous créez des webhooks avec des paramètres presque identiques ou si vous avez deux webhooks identiques dans l'environnement de production et dans l'environnement sandbox, ces derniers auront des clés différentes ; assurez-vous donc que la clé que vous allez utiliser est la clé correspondante.
Une fois que vous avez le timestamp
, la Signature Cryptr v1
(ou v0
) et la signature_key
, vous pouvez passer à l'étape suivante.
3. Signer l'événement que vous avez reçu
Récupérer l'événement à signer
Désormais, chaque fois que vous recevrez un événement, vous disposerez de toutes les informations nécessaires. Pour commencer, voici à quoi ressemble la charge utile d'un événement :
{
"__domain__": "shark-academy",
"__environment__": "sandbox",
"__type__": "Event",
"code": "dir_sync.user.update.success",
"data": {
"__domain__": "synchronizable",
"__type__": "DirectorySyncEvent",
"change": "update",
"directory_sync_id": "6aefc945-0815-4631-8ded-edc7bb1944ae",
"provider": "okta",
"resource": {
"changes": {
"new_values": {
"active": true,
"updated_at": "2023-10-17T08:12:37"
},
"previous_values": {
"active": false,
"updated_at": "2023-10-17T08:11:38"
}
},
"id": "7e2f977c-a8f0-4451-8967-fe63b243e7a1",
"type": "User"
}
},
"errors": null,
"issued_at": "2023-10-17T08:12:38.113207Z",
"params": {"active": true, "id": "7e2f977c-a8f0-4451-8967-fe63b243e7a1"},
"webhook_id": "webhook_2Wsnp8azBTeK2r29TExX9vBvnCg"
}
Vous pouvez utiliser l'identifiant webhook_id
contenu dans ce payload pour vous aider à récupérer les informations correctes à l'étape B.
Préparer la chaîne à signer
Pour créer la chaîne à signer, il faut ensuite concaténer plusieurs éléments, les éléments à concaténer sont les suivants :
- L'horodatage (sous forme d'une chaîne)
- Le caractère
.
- La forme Stringifié du payload JSON (le contenu de la requête reçu)
Ce qui donnerait quelque chose comme ceci :
require 'json'
stringifiedPayload = JSON.generate(payload)
stringToSign = timestamp + "." + stringifiedPayload
# => "1676905124.{\"__domain__\":\"shark-academy\",\"__environment__\":\"sandbox\",\"__type__\":\"Event\",...}"
Nous devons maintenant signer cette chaîne.
Signer la chaîne résultante
Pour signer la chaîne obtenue précédemment, utilisez la fonction de hachage SHA256
avec la signature_key
associée à votre webhook comme secret. Vous obtiendrez ainsi un HMAC
que vous pourrez comparer aux valeurs de signatureV1
ou de signatureV0
obtenues à l'étape A.3.
# Create a digest for the SHA256 Algorithm
digest = OpenSSL::Digest.new('sha256')
# Generate the HMAC of your previously constructed string using your Webhook signature_key
hmac = OpenSSL::HMAC.digest(digest, signature_key, stringToSign)
# Encode the Result in Base64 with no padding /!\
yourSignature = Base64.urlsafe_encode64(hmac, false)
Assurez-vous que:
- vous utilisez l'algorithme SHA 256
- Votre charge utile n'a pas été réécrite (c'est-à-dire que l'ordre des clés est le même que lorsque vous l'avez reçue).
- Vous avez désactivé le padding
- Vous avez stringifié votre charge utile
4. Comparer la signature obtenue avec la signature fournie
Maintenant que vous avez établi la signature de la charge utile reçue, vous pouvez la comparer à celle envoyée par Cryptr.
# Compare the Signature sent by Cryptr with yours
if yourSignature == signatureV1
# You can proceed
else
# Signatures are not equal
end
Si les deux signatures sont similaires, vous pouvez supposer que la charge utile que vous avez reçue a été envoyée par Cryptr.
Si vous recevez une signature correcte, vérifiez la date et l'heure auxquelles elle a été délivrée. Si la date est trop éloignée, vous pouvez la refuser.
Exemple complet
Voici un exemple complet:
require 'json'
## STEP 1.Get the necessary data ##
# Get the Cryptr Signature Key
cryptrSignature = request.env["HTTP_CRYPTR_SIGNATURE"]
# Extract the timestamp
array = cryptrSignature.split
timestamp = array.first
# Extract the signature
signatureV1 = array[1]
signatureV1.slice! "sha256."
# OPTIONAL: Get the Signature V0 from splitted signature
# signatureV0 = array[2]
# signatureV0.slice! "sha256."
## STEP 2. Use your key
# See "Retrieve your Secret Key before" part
## STEP 3. Sign your data
payload = request.body.read
# Prepare the string to sign
stringifiedPayload = JSON.generate(payload)
stringToSign = timestamp + "." + stringifiedPayload
# Sign the result
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.digest(digest, signature_key, stringToSign)
yourSignature = Base64.urlsafe_encode64(hmac, false)
## STEP 4. Compare the signature ##
if yourSignature == signatureV1
# You can proceed
else
# Signatures are not equal
end
Comment tester un Webhook
Pour déclencher un événement, vous pouvez utiliser notre déclencheur d'événement Webhook. Pour cela, vous pouvez utiliser la requête suivante :
curl -X POST '${cryptr_service_url}/api/v2/webhook-events/trigger-test' \
-d 'event_code=dir_sync.user.provision.success'
Erreurs Webhook
Lors de la synchronisation, il peut arriver qu'un utilisateur ne puisse pas être créé ou mis à jour. Dans ce cas, le champ errors
peut vous aider.
Par exemple, il apparaîtra comme suit :
"errors": {
"phone_numbers": {
"data": [
{
"type": "work",
"value": "+00000",
"primary": true,
"phone_country_code": null,
"phone_national_number": null
}
],
"error": [
{
"value": [
"Invalid country calling code"
]
}
]
}
}
Les données reçues sont indiquées dans la première map phone_numbers
, mais celles-ci posent problèmes.
La seconde map error
est la raison de l'erreur.
Voici une liste d'erreurs possibles que vous pouvez rencontrer dans votre payload d'événement :
Erreur | Signification |
---|---|
Invalid country calling code | Le code pays du numéro de téléphone n'a pas été reconnu. |
invalid_format | Les données fournies pour le champ ne sont pas valides. (Int au lieu de float, email sans @...) |
has already been taken | La valeur a déjà été prise pour ce champ et l'unicité est requise.. |
The string supplied did not seem to be a phone number | Le numéro de téléphone fourni n'est pas valide (le plus souvent, il s'agit d'un numéro vide). |