Deploy your first Webhook
1. Create a Webhook endpoint
Once you know your needs, you can create an endpoint that will serve as a receiver for your Webhooks. We will send the Events to this one using the HTTP(S) URL that you will have provided when creating your Webhooks. A simple Ruby Endpoint example might look like this:
require 'sinatra'
require 'json'
post '/webhook' do
puts request.body.read
end
This example was created using the Sinatra library. Here, we create a /webhook
route that will listen to HTTP POST
requests.
2. Process received events
The previously created Endpoint must be able to retrieve the events sent, process them and return a 2xx response.
Once events are sent by Cryptr our services wait for a response. If the response is a 2xx response, we assume that you have received the event and we will not try to send it again.
If, on the other hand, you send us a different answer, we will try to resend the events to you at certain time intervals. To learn more about the different responses that you can send us and their actions, you can consult our How to properly manage your webhooks section which deals with this subject.
{
"__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"
}
Response
To ensure that we receive your confirmation response in the shortest possible time, you must send a response as soon as possible. You must process the sending of the response first before doing any other processing.
Retries
All 3xx
, 4xx
and 5xx
codes will be processed by Cryptr to send you an event again at different intervals which you can consult in our How to properly manage your webhooks. If we don't get a response or if we don't get it in time, we'll assume your Endpoint doesn't exist and won't send the event again.
3. Create a test webhook
To use Webhooks you should create one using API or your Cryptr Dashboard. Here is the Webhook page where you can create a Webhook quickly:
This Webhook will listen to all Direcotry Sync Events. If you wish, you can change the event codes to listen to by changing the value of the event_codes
key (currently only available by API). To see a full list of the event_codes
available you can consult our Events List section.
4. Test the correct reception of the events
Check that your Webhook is working properly. Are the events well received?
Now that your Webhook is created, you can test an event corresponding to an event listened to by your Webhook. In the case of a Webhook that listen to Directory Sync event you can for example test the event dir_sync.user.provision.success
.
To do this, we provide you with an endpoint to trigger the sending of false events. To find out more, see How to test a Webhook or our API documentation.
If you used the same Endpoint as shown in our example in step 2, you should see the body of the POST
request that we sent to you in your terminal.
5. Deploy your endpoints on your services
If testing is successful, deploy your Webhook Endpoint to your production services.
Once the configuration is tested, you can repeat the previous steps on your production environment. It is possible to create several different webhooks listening to several different events but also to define different target Endpoints for each of your Webhooks. For example, a Webhook that sends SCIM events to an Endpoint /scim-webhook
and another that sends SSO events to an Endpoint /sso-webhook
.
How to properly manage your webhooks
It is important to manage your Webhooks well. For this there are some tips and information that could be useful to you.
Select the events you want to receive
A smart selection of events received is the first thing to do. Think carefully about what events really matter to you. Indeed, having a webhooks that manage all the events can be tempting but it could quickly overload your Endpoint and make reading complicated. That's why it's better to focus on quality over quantity by limiting yourself to the events you really need.
For example, if I need to know when a User is created through SCIM, I have to listen to the event dir_sync.user.provision.success
.
The different status codes that you can return
The first thing to do when you receive an event is, of course, to send us a response with a 2xx code. But you can also use other codes that will be taken into account differently at Cryptr.
2xx codes
All codes between 199 and 300 not included are codes signifying successful sending. Cryptr will then interpret these responses as such and suppose that you have received the event.
410 Code
The 410 code is used to inform us that you wish to cancel the sending of an event. If you send us a 410 code then we will stop sending you this event.
4xx codes
For all other 4xx codes Cryptr will attempt to resend the event to you at different intervals which are as follows:
Initial attempt | 0 |
---|---|
Attempt #1 | 5 minutes |
Attempt #2 | 10 minutes |
Attempt #3 | 20 minutes |
Attempt #4 | 1 hour |
Attempt #4 | 2 hours |
503 retry-after
This option, which can be included in the HTTP header of your HTTP response you send us, allows you to define yourself a duration after which we will retry to send you the event.
To do this, simply send to us a 503 response code and include in the http header of your HTTP response the key retry-after
followed by a value in seconds. For example:
{“retry-after”, 60}
This will restart sending an event to your Endpoint after 60 seconds.
Events List
If you want to receive all Directory Sync events, you can simply fill in dir_sync.all
in the event_codes
key instead of the list of event codes.
Directory Sync Event Codes
Event | Event_Code |
---|---|
Directory Sync activated | dir_sync.activate.success |
Directory Sync deactivated | dir_sync.deactivate.success |
User created through Directory Sync | dir_sync.user.provision.success |
User updated through Directory Sync | dir_sync.user.update.success |
User deleted through Directory Sync | dir_sync.user.deprovision.success |
User creation through Directory Sync failed | dir_sync.user.provision.fail |
User update through Directory Sync failed | dir_sync.user.update.fail |
User deletion through Directory Sync failed | dir_sync.user.deprovision.fail |
Group created through Directory Sync | dir_sync.group.provision.success |
Group updated through Directory Sync | dir_sync.group.update.success |
Group deleted through Directory Sync | dir_sync.group.deprovision.success |
Group creation through Directory Sync failed | dir_sync.group.provision.fail |
Group update through Directory Sync failed | dir_sync.group.update.fail |
Group deletion through Directory Sync failed | dir_sync.group.deprovision.fail |
User activated through Directory Sync | dir_sync.user.activate.success |
User deactivated through Directory Sync | dir_sync.user.deactivate.success |
User activation through Directory Sync failed | dir_sync.user.activate.fail |
User deactivation through Directory Sync failed | dir_sync.user.deactivate.fail |
Group activated through Directory Sync | dir_sync.group.activate.success |
Group deactivated through Directory Sync | dir_sync.group.deactivate.success |
Group activation through Directory Sync failed | dir_sync.group.activate.fail |
Group deactivation through Directory Sync failed | dir_sync.group.deactivate.fail |
Webhook Security
Each event sent to your Endpoint(s) by Cryptr contains a signature. Security is an important concept, which is why this signature has been added. You can ignore it if you wish, but it is not recommended to do so. Indeed, this key will allow you to verify that the events received have indeed been sent by Cryptr and not by an outside person.
1. Get the necessary data
Get the Cryptr Signature Key
The Cryptr Signature is located in the header of the HTTP request under the cryptr-signature
key. Indeed, when you receive an HTTP request or response, the payload is made up of HTTP protocol information, such as headers, a URL, body content, version and status information.
This is what an HTTP header looks like:
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
For example, you can retrieve the cryptr-signature
by performing the following function:
cryptrSignature = request.env["HTTP_CRYPTR_SIGNATURE"]
Here is an example of a Cryptr signature:
cryptr-signature: v1=640e3f227fed2123e84101cc69f41fba3435ec3b8f28d4ea249231f928efa990,v0=3d5990d850a7bbe430bf6ed93329f5b61b3fb72871afed9a071f3b95317d73ab | ,
---|
Extract the timestamp
Each cryptr-signature
contains a timestamp
. This timestamp, which you'll need to retrieve & use, prevents replay attacks.
You can proceed as follows to retrieve the timestamp:
# Split the cryptr-signature
array = cryptrSignature.split
# Get the timestamp from the splitted signature
timestamp = array.first
Extract the signature
Each cryptr-signature
also contains a v1
Signature. But it can also contains a v0
Signature if your secret has been updated.
Signature v1
is the most recent signature, signature v0
is the signature made with the previous signature_key
. This was thought so that you have time to make the necessary changes without being blocked if the signature_key
changes. Although both versions are usable, it is strongly recommended to use only v1
whenever possible.
To retrieve your signatures, proceed as follows:
# 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."
Note that we don't take the raw value of the signature, but only what comes after the sha256.
.
⚠️ Don't forget the .
.
Once that's done, we can move on to the next step.
2. Retrieve your Secret Key
To verify your signatures, you will need to retrieve the signature_key
of the webhooks for which you want to ensure the authenticity. If you don't remember it from when you created your webhooks, you can always retrieve it via the following query:
curl 'https://${cryptr_service_url}/api/v2/webhooks/${webhook_id}'
This will give you the following result:
{
"__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"
}
Each webhook you create will have a different signature_key
. Even if you create webhooks with almost identical parameters or you have two identical webhooks in production and in sandbox environment, these will have different keys so be sure to check that the key you are going to use is the corresponding key.
Once you have the timestamp
, the v1
(or v0
) Cryptr Signature and the signature_key
, we can move on to the next step. Signing the event you've received.
3. Sign your data
Get the Event to Sign
Now, each time you receive an Event, you'll have all the information you need. To start with, here's what an event payload looks like:
{
"__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"
}
You can use the webhook_id
contained in this payload to help you retrieve the correct information in step B.
Prepare the string to sign
To create the string to sign, you must then concatenate several elements, the elements to concatenate are as follows:
- The timestamp (in string format)
- The character
.
- The Stringified JSON payload (the content of the request you received)
Which would give something like this:
require 'json'
stringifiedPayload = JSON.generate(payload)
stringToSign = timestamp + "." + stringifiedPayload
# => "1676905124.{\"__domain__\":\"shark-academy\",\"__environment__\":\"sandbox\",\"__type__\":\"Event\",...}"
We now need to sign this string.
Sign the resulting string.
To sign the previously obtained string, use the SHA256
hash function with the signature_key
associated with your webhook as the secret. This will give you an HMAC
which you can then compare with the values of signatureV1
or signatureV0
obtained in step 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)
Make sure that:
- you are using the SHA 256 algorithm
- Your payload has not been rewritten (i.e. that the key order is the same as when you received it)
- You have disabled padding
- You have stringified your payload
4. Compare the signature obtained with the signature provided
Now that you have established the signature of the received payload you can compare it with the one sent by Cryptr.
# Compare the Signature sent by Cryptr with yours
if yourSignature == signatureV1
# You can proceed
else
# Signatures are not equal
end
If the two signatures are similar, you can assume that the payload you received was sent by Cryptr.
If you receive a correct signature, check the date and time it was issued. If the date is too far away, you can choose to refuse it.
Full example
Here is a full example:
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
How to test a Webhook
To trigger an event, you can use our Webhook Event triggering. For this you can use the following query:
curl -X POST '${cryptr_service_url}/api/v2/webhook-events/trigger-test' \
-d 'event_code=dir_sync.user.provision.success'
Webhook Errors
Sometimes during synchronization, it can happen that a user cannot be created or updated, for example. In such cases the errors
field can help you.
For example it will appear like this:
"errors": {
"phone_numbers": {
"data": [
{
"type": "work",
"value": "+00000",
"primary": true,
"phone_country_code": null,
"phone_national_number": null
}
],
"error": [
{
"value": [
"Invalid country calling code"
]
}
]
}
}
The data received is reflected in the first map phone_numbers
, but there are issues with it.
The second map error
is the reason for the error.
Here is a list of possible errors you may encounter in your Event payload:
Error | Meaning |
---|---|
Invalid country calling code | The country code for the phone number was not recognized. |
invalid_format | The data provided for the field is not valid. (Int instead of float, email without @...) |
has already been taken | The value has already been taken for this field and uniqueness is required for it. |
The string supplied did not seem to be a phone number | The provided phone number is invalid (most often null is provided) |