Skip to main content
Follow these steps on how to set up a webhook and get started to create a webhook that sends real-time data.

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:

webhook-server.rb
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.

Example of received event
{
"__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:

Cryptr Dashboard - Create a Webhook

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 attempt0
Attempt #15 minutes
Attempt #210 minutes
Attempt #320 minutes
Attempt #41 hour
Attempt #42 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

info

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

EventEvent_Code
Directory Sync activateddir_sync.activate.success
Directory Sync deactivateddir_sync.deactivate.success
User created through Directory Syncdir_sync.user.provision.success
User updated through Directory Syncdir_sync.user.update.success
User deleted through Directory Syncdir_sync.user.deprovision.success
User creation through Directory Sync faileddir_sync.user.provision.fail
User update through Directory Sync faileddir_sync.user.update.fail
User deletion through Directory Sync faileddir_sync.user.deprovision.fail
Group created through Directory Syncdir_sync.group.provision.success
Group updated through Directory Syncdir_sync.group.update.success
Group deleted through Directory Syncdir_sync.group.deprovision.success
Group creation through Directory Sync faileddir_sync.group.provision.fail
Group update through Directory Sync faileddir_sync.group.update.fail
Group deletion through Directory Sync faileddir_sync.group.deprovision.fail
User activated through Directory Syncdir_sync.user.activate.success
User deactivated through Directory Syncdir_sync.user.deactivate.success
User activation through Directory Sync faileddir_sync.user.activate.fail
User deactivation through Directory Sync faileddir_sync.user.deactivate.fail
Group activated through Directory Syncdir_sync.group.activate.success
Group deactivated through Directory Syncdir_sync.group.deactivate.success
Group activation through Directory Sync faileddir_sync.group.activate.fail
Group deactivation through Directory Sync faileddir_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:

Header
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:

ruby
cryptrSignature = request.env["HTTP_CRYPTR_SIGNATURE"]

Here is an example of a Cryptr signature:

cryptr-signature: t=1676905124,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:

ruby
# 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:

ruby
# 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."
info

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
curl 'https://${cryptr_service_url}/api/v2/webhooks/${webhook_id}'

This will give you the following result:

Webhook Payload
{
"__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:

Example of an event received
{
"__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"
}
note

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:

Ruby
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.

Ruby
# 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)
warning

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.

Ruby
# 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:

Ruby
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
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 example
  "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:

ErrorMeaning
Invalid country calling codeThe country code for the phone number was not recognized.
invalid_formatThe data provided for the field is not valid. (Int instead of float, email without @...)
has already been takenThe value has already been taken for this field and uniqueness is required for it.
The string supplied did not seem to be a phone numberThe provided phone number is invalid (most often null is provided)