Event webhooks
What are Setyl event webhooks?
Event hooks in Setyl are like notifications that are sent out when specific events happen in your organization. These notifications are in the form of messages sent over the internet to a web address that you provide. These messages contain information about the events in a specific format called JSON.
Think of these Setyl event hooks as triggers that can start processes in your own software systems. To make use of these event hooks, you’ll need to create a special online service that can receive these messages. You’re responsible for creating this service and making sure it’s available on the internet, separate from Setyl.
Setyl event hooks are based on the idea of webhooks, which is a common practice in the industry.
You’re allowed to set up a maximum of 5 active and confirmed event hooks in your organization at once. Each of these event hooks can be set up to notify you about multiple types of events.
Which events can you choose?
When you’re setting up an event hook for the first time, you get to pick which types of events it will send notifications for. The options you can choose from are a selection of the different kinds of events that Setyl keeps track of.
These events can involve changes related to people, assets, licenses, and other modifications within Setyl. For instance, you could set up an event hook to let you know whenever a new person is added. This way, you can use these hooks to kick off actions you need to perform internally whenever a new person becomes part of your system. This might include tasks like creating a support ticket or sending out an email message.
Full list of currently supported events (the list will be updated):
- People
person_added
: New person addedperson_added_via_integration
: New person added through integration with an external systemperson_updated
: Person updatedperson_updated_via_integration
: Person updated via integrationperson_archived
: Person archivedperson_reinstated
: Person reinstatedperson_merged
: Person merged
- Assets
asset_added
: New asset addedasset_updated
: Asset updatedasset_archived
: Asset archivedasset_reinstated
: Asset reinstatedasset_linked
: Asset linkedasset_unlinked
: Asset unlinked
- Licenses
license_added
: New license addedlicense_updated
: License updatedlicense_archive
: License archivedlicense_reinstated
: License reinstated
Requests sent by Setyl
Once events that fit the event types you’ve selected happen in your organization, the event hook gets activated on its own and sends a request to your outside service. This request comes with a payload in JSON format that contains details about the event. The structure of this JSON payload is similar to what you’d get when you ask for data through the Setyl API. A sample JSON payload is provided in “Sample event delivery payload”.
These requests that Setyl sends to your outside service can either be HTTPS or HTTP requests. The ongoing delivery of events is handled through POST requests, while a one-time GET request is used to confirm your endpoint.
One-time verification request
Once you’ve set up and activated your webhook endpoint, Setyl will send a single GET request to your endpoint as a validation step. This request includes a validation value that your service must send back. This verification is a way to ensure that you have control over the endpoint.
It’s important to note that this one-time validation request is the only time Setyl will use a GET request for your external service. The ongoing notifications about events will be sent through HTTPS or HTTP POST requests. To handle this special validation request differently, your web service can distinguish between GET and POST.
Here’s how your service should handle the validation:
- The request from Setyl will have an HTTP header called
X-Setyl-Verification-Challenge
. - Your service needs to read the value from this header and return it within the response body as a JSON object named “verification”:
{ "verification": "value_from_header" }
. - The
value_from_header
is obtained from the request’s HTTP header, but you need to send it back enclosed in a JSON object.
Here’s an example of Node.js code that demonstrates this one-time verification process:
// Event hook initial verification
// Extract header 'x-setyl-verification-challenge' from Setyl request
// Return value as JSON object verification
app.get("/setylCallback", (request, response) => {
var returnValue = {
"verification": request.headers['x-setyl-verification-challenge'],
};
console.log("Event hook verification request received.")
response.json(returnValue);
});
Ongoing event delivery
Once the validation is done, for ongoing event notifications, Setyl will use HTTPS or HTTP POST requests to send information to your service. The JSON content in these requests contains specific details about the events that took place.
This content is structured as an object corresponding to the event type. It’s the same type of object that the Setyl API outlines for this particular entity. You can refer to the Setyl API documentation to learn more about this object.
Setyl does its best to deliver events reliably. The events are guaranteed to be delivered at least once. However, delivery might be slowed down by network conditions. Sometimes, multiple requests could arrive together after a delay, or events might not arrive in the order they occurred. To ensure proper ordering, you can use the time stamp found in the X-Setyl-Event-Fired-At
HTTP header of each event. To identify duplicate deliveries, you can compare the X-Setyl-Event-UUID
value of incoming events with the values of previously received events.
It’s important to note that there’s no specific promise regarding the maximum time between when an event happens and when it’s delivered.
Timeout and Retry
When Setyl contacts your external service, it maintains a standard timeout of 3 seconds. In case of failures, Setyl will attempt retries using an exponential backoff strategy. It will make 10 retry attempts over a period of roughly 4 hours and 30 minutes. However, responses with a status code in the 4xx range won’t be retried.
Note: It’s crucial that your external service can provide responses to Setyl’s requests within the 3-second timeout restriction.
Note: If the maximum number of retries is reached or if your server returns a 4xx status code, the webhook endpoint will be deactivated, and no further events will be dispatched to it.
For more details on the HTTP responses you need to send, refer to “Responses to Ongoing Event Delivery Requests”.
Security
For security purposes, it’s advisable to restrict requests to only those originating from Setyl. One of the simplest ways to achieve this is by establishing a secret token that can be used to verify the authenticity of the information.
The secret token is created automatically when you register your webhook endpoint in Setyl. You can find it in the form when you’re creating the endpoint, as well as in the form when you’re editing the endpoint later on.
Next, you should configure an environment variable on your server to store this token securely. This token will be used later on to verify the signature of the webhook payload, ensuring its authenticity.
Once your secret token is configured, Setyl employs it to generate a hash signature for every payload. This hash signature is then included in the headers of each request under the label X-Setyl-Signature
.
You should compute a hash using your SECRET_TOKEN
and make sure that the resulting hash matches the hash provided by Setyl. Setyl employs an HMAC hex digest method to compute this hash.
While your language and server implementations might not exactly match the examples provided, there are several crucial points to highlight:
Regardless of your implementation, the hash signature always commences with
sha256=
. This employs your secret token’s key and the payload body.Utilizing a simple
==
operator is not recommended. Instead, opt for a method like secure_compare or crypto.timingSafeEqual. These methods facilitate “constant time” string comparisons, which assist in mitigating specific timing attacks that can exploit regular equality operators or loops in Just-In-Time optimized languages.
Testing Values
No matter which programming language you use for implementing HMAC verification in your code, you can utilize the following secret and payload values to validate the correctness of your implementation:
Secret: “It’s a Secret to Everybody”
Payload: “Hello, World!”
If your implementation is accurate and employs the SHA-256 algorithm, the signatures you generate should align with the following signature values:
Generated Signature:
757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
Expected X-Setyl-Signature:
sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
Ruby Example
Here’s an example of how you could define a verify_signature function in Ruby:
def verify_signature(payload_body)
signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['X-Setyl-Signature'])
end
You can then call this function when you receive a webhook payload:
post '/payload' do
request.body.rewind
payload_body = request.body.read
verify_signature(payload_body)
push = JSON.parse(payload_body)
"I received some JSON: #{push.inspect}"
end
Python Example
Here’s an example of how you can define the verify_signature
function in Python and use it to verify signatures when receiving a webhook payload:
import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
parts = signature_header.split("=")
if len(parts) != 2 or parts[0] != "sha256":
raise Exception("Invalid signature header format")
received_signature = parts[1]
expected_signature = hmac.new(secret_token.encode("utf-8"), payload_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected_signature, received_signature):
raise Exception("Signatures didn't match")
# Usage example:
payload_body = "your_payload_here"
secret_token = "your_secret_token"
signature_header = "sha256=your_received_signature"
try:
verify_signature(payload_body, secret_token, signature_header)
print("Signature verified successfully")
except Exception as e:
print("Signature verification failed:", e)
Node.js Example
Here’s an example of how you can define the verifySignature
function in Node.js and use it to verify signatures when receiving a webhook payload:
async function verifySignature(secret, header, payload) {
const [algorithm, sigHex] = header.split("=");
const keyBytes = new TextEncoder().encode(secret);
const key = await crypto.subtle.importKey(
"raw",
keyBytes,
{ name: "HMAC", hash: { name: "SHA-256" } },
false,
["verify"]
);
const sigBytes = hexToBytes(sigHex);
const dataBytes = new TextEncoder().encode(payload);
return await crypto.subtle.verify(
algorithm,
key,
sigBytes,
dataBytes
);
}
function hexToBytes(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return bytes;
}
// Usage example:
const secret = "your_secret_here";
const header = "sha256=your_received_signature";
const payload = "your_payload_here";
verifySignature(secret, header, payload)
.then(result => {
if (result) {
console.log("Signature verified successfully");
} else {
console.log("Signature verification failed");
}
})
.catch(error => {
console.error("Error during signature verification:", error);
});
Responses to Ongoing Event Delivery Requests
Your external service should respond to ongoing POST requests with an empty body and an HTTP status code of 200 (OK) or 204 (No Content).
To ensure efficiency, it’s recommended that you promptly return the HTTP response without waiting for any of your internal processes that might be triggered by the event to finish.
Setting Up Webhooks
The fundamental steps for registering and confirming a new webhook endpoint, along with configuring events for it, involve the following actions:
- Develop your external web service to accept incoming event webhook calls from Setyl.
- Register the endpoint of your external service with Setyl and specify event hook settings.
- Activate the endpoint (as part of this process, Setyl will send a GET request to your external service to confirm your control over the endpoint).
To register an endpoint and setting up webhooks, please visit the Company Settings page.
Sample event delivery payload
The following is an example of a JSON payload of a request from Setyl to your external service:
[
{
"url": "https://setyl.test/api/v1/people/f012b69d-8aba-4445-ae8f-4e995b2582ad",
"view_url": "https://setyl.test/users/f012b69d-8aba-4445-ae8f-4e995b2582ad",
"uuid": "95c34649-9ba0-47af-9485-8274e4cd9992",
"first_name": "Harvey",
"last_name": "Specter",
"email": "harvey@specter.com",
"phone": "+381631111111",
"job_title": "Lawyer",
"avatar_url": null,
"notes": null,
"address": "1050 Pittsford Victor Rd, Pittsford, New York(NY), 14534",
"role_name": "employee",
"human_role_name": "Employee",
"personal_identifier": 12345,
"login_username": null,
"join_date": "2021-08-11 12:04:51 UTC",
"leave_date": null,
"last_sign_in_at": null,
"updated_at": "2023-08-11 11:54:51 UTC",
"created_at": "2023-08-11 11:54:51 UTC",
"state_name": "active",
"human_state_name": "Active",
"state_events": [
"mark_as_onboarding",
"mark_as_active",
"mark_as_offboarding"
],
"locations_string": "10 Downing Street, London, GB",
"locations": [
{
"name": "10 Downing Street, London, GB,",
"uuid": "a293dad6-d680-40ba-b5c8-f8ee0766dede",
"source_type": "microsoft-dynamics-hr",
"human_source_type": "Microsoft Dynamics 365 Human Resources"
}
],
"departments_string": "Department for Audit",
"departments": [
{
"name": "Department for Audit",
"uuid": "0b1ffabb-051c-4540-a031-fc78cd4c92a8",
"source_type": null,
"human_source_type": null
}
],
"legal_entities_string": "LE for Audit",
"legal_entities": [
{
"name": "LE for Audit",
"uuid": "4baf9ea5-e2d3-4986-a718-3a97f472bdb1",
"source_type": null,
"human_source_type": null
}
]
}
]