Create, Configure a Webhook
Webhook Connectors Tutorial: from Webhook creation to start sending Events.
Webhooks (also called a web callback or HTTP push API) are the best way to notify third-party tools with real-time information from HrFlow.ai. The HrFlow.ai Webhooks define a callback URL where Events are delivered as they happen in HrFlow.ai.
Prerequisites
- Having an HrFlow.ai account. To signup, please visit https://hrflow.ai/signup
Step 1: Go to the Connectors Marketplace
Step 2: Choose Webhook
After opening the modal, click on the button Β«InstallΒ».
Step 3: Choose an Event Type
Webhook Format
The webhook posts data to the URL you provided in the configuration. The body is encoded in JSON, which is indicated by the application/json content-type, with UTF-8 encoding (as stated in RFC 4627 (http://www.ietf.org/rfc/rfc4627.txt).
1. Profile's Events
1.1 Profile's Event Types
Event Type | Description | Volume |
---|---|---|
profile.parsing.success | Sent when a Profile is parsed successfully for the 1st time. | High volume |
profile.parsing.update | Sent when an existing Profile is parsed again successfully. | Medium volume |
profile.parsing.error | Sent when parsing a Profile has failed and should be retried. The Profile Parsing cannot be retrieved. | Low volume |
profile.storing.success | Sent when a Profile is saved successfully for the 1st time. | High volume |
profile.storing.update | Sent when an existing Profile is updated successfully. | Medium volume |
profile.storing.error | Sent when saving a Profile has failed and should be retried. The Profile cannot be retrieved. | Low volume |
profile.searching.success | Sent when a Profile is saved successfully in the Searching API Index for the 1st time and is ready to be queried. | High volume |
profile.searching.update | Sent when an existing Profile is updated successfully in the Searching API Index and is ready to be queried. | Medium volume |
profile.searching.error | Sent when saving a Profile in the Searching API Index has failed and should be retried. The Profile cannot be queried. | Low volume |
profile.scoring.success | Sent when a Profile is saved successfully in the Scoring API Index for the 1st time and is ready to be scored. | High volume |
profile.scoring.update | Sent when an existing Profile is updated successfully in the Scoring API Index and is ready to be scored. | Medium volume |
profile.scoring.error | Sent when saving a Profile in the Scoring API Index has failed and should be retried. The Profile cannot be scored. | Low volume |
1.2 Profile's Event Payloads
{status}
= success
, update
or error
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:profile.parsing.success
origin=api
message=profile parsing succeed
profile={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "source": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=profile.parsing.success&origin=api&message=profile+parsing+succeed&profile=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:profile.storing.success
origin=api
message=profile storing succeed
profile={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "source": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=profile.storing.success&origin=api&message=profile+storing+succeed&profile=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:profile.searching.success
origin=api
message=profile searching succeed
profile={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "source": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=profile.searching.success&origin=api&message=profile+searching+succeed&profile=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:profile.scoring.success
origin=api
message=profile scoring succeed
profile={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "source": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=profile.scoring.success&origin=api&message=profile+scoring+succeed&profile=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
2.1 Job's Event Types
Event Type | Description | Volume |
---|---|---|
job.storing.success | Sent when a Job is saved successfully for the 1st time. | High volume |
job.storing.update | Sent when an existing Job is updated successfully. | High volume |
job.storing.error | Sent when saving a Job has failed and should be retried. The Job cannot be retrieved. | Low volume |
job.searching.success | Sent when a Job is saved successfully in the Searching API Index for the 1st time and is ready to be queried. | High volume |
job.searching.update | Sent when an existing Job is updated successfully in the Searching API Index and is ready to be queried. | High volume |
job.searching.error | Sent when saving a Job in the Searching API Index has failed and should be retried. The Job cannot be queried. | Low volume |
job.scoring.success | Sent when a Job is saved successfully in the Scoring API Index for the 1st time and is ready to be scored. | High volume |
job.scoring.update | Sent when an existing Job is updated successfully in the Scoring API Index and is ready to be scored. | High volume |
job.scoring.error | Sent when saving a Job in the Scoring API Index has failed and should be retried. The Job cannot be scored. | Low volume |
2.2 Job's Event Payloads
{status}
= success
, update
or error
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:job.parsing.success
origin=api
message=job parsing succeed
job={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "board": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=job.parsing.success&origin=api&message=job+parsing+succeed&job=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:job.storing.success
origin=api
message=job storing succeed
job={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "board": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=job.storing.success&origin=api&message=job+storing+succeed&job=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:job.searching.success
origin=api
message=job searching succeed
job={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "board": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=job.searching.success&origin=api&message=job+searching+succeed&job=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
# Headers
content-type=application/x-www-form-urlencoded
# Form values
type:job.scoring.success
origin=api
message=job scoring succeed
job={"key": "d821393853fc32b08c93b8d38590817c72048ec4", "board": {"key": "d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8"}}
# Raw content
type=job.scoring.success&origin=api&message=job+scoring+succeed&job=%7B%22key%22%3A+%22d821393853fc32b08c93b8d38590817c72048ec4%22%2C+%22source%22%3A+%7B%22key%22%3A+%22d900ec70c67d43c71027f9bc63ec3b5b3e16c1d8%22%7D%7D
succeed
Step 3: Specify an Callback URL
For this tutorial, let's create a fake Webhook URL on an online website (such as RequestCatcher or Webhook dot site) and then send an Event of the type profile.parsing.success
.
The Webhook URL is ready to receive requests.
Now let us:
- Pick the Event of the type
profile.parsing.success
from the Dropdown (don't hesitate to try others) - Choose a recognizable Webhook name that you can easily remember, and succeedother users can easily recognize.
- Copy the Webhook URL in the Settings and click on the button Β«SaveΒ».
You can also simulate the webhook request by clicking on the button Β«CheckΒ» before saving.
You can click on the button Β«Add more webhooksΒ» to send more Events.
The RequestCatcher website shows you that HrFlow.ai has saved your event has successfully.
From now, Every time you save a profile in HrFlow.ai, a request will be sent to this Webhook URL.
Don't forget to delete unused Webhook Events to let your workspace clean.
Advanced Topics:
1. Securing Webhooks with Signature
When a Webhook Event is sent, an HTTP POST request is made to your specified Webhook URL
. This POST request will contain some parameters, including the HTTP-HRFLOW-SIGNATURE
header parameter, which you can use for authorization.
The HTTP-HRFLOW-SIGNATURE
is base64url encoded and signed with an HMAC version of your WEBHOOK SECRET KEY
with the SHA-256 digest.
HMAC algorithm with SHA-256 digest
See examples in different languages https://www.jokecamp.com/blog/examples-of-creating-base64-hashes-using-hmac-sha256-in-different-languages .
What this means is that when it is POSTed to your WEBHOOK SECRET KEY, you will need to parse and verify it before it can be used. This is performed in three steps:
- Split the signed request into two parts delineated by a '.' character (eg. 238fsdfsd.oijdoifjsidf899)
- Decode the first part - the encoded signature - from base64url
- Decode the second part - the payload - from base64url and then decode the resultant JSON object
These steps are possible in any modern programming language.
Examples:
<?php
function parse_signed_request($signed_request, $secret) {
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
// decode the data
$sig = base64_url_decode($encoded_sig);
$data = json_decode(base64_url_decode($payload), true);
// confirm the signature
$expected_sig = hash_hmac('sha256', $payload, $secret, $raw = true);
if ($sig !== $expected_sig) {
error_log('Bad Signed JSON signature!');
return null;
}
return $data;
}
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
import hmac
import hashlib
import json
import urllib
def check_signature(secret, signature, body_str):
body_dict = urllib.parse.parse_qs(body_str) # = {'team_name': ['yourteam'], 'type': ['profile.storing.success']}
body_dict = {key:value[0] for key, value in body_dict.items()} # = {'team_name': 'yourteam', 'type': 'profile.storing.success'}
# WARNING: the separators parameter is important to ensure there are no spaces in the encoded string
# encoded_body = '{"team_name":"yourteam","type":"profile.storing.success"}'
encoded_body = json.dumps(body_dict, separators=(',', ':'))
hasher = hmac.new(secret.encode('utf8'), encoded_body.encode('utf8'), hashlib.sha256)
dig = hasher.hexdigest()
return hmac.compare_digest(dig, signature)
# WEBHOOK_SECRET = "wsk_...."
signature = "91b3d1667cf420806512fb61b69f04af6fe9b71505e8c3acedeee4cc9a71017b" # in the header
body_str = "team_name=yourteam&type=profile.storing.success" # in the body for example
print(check_signature(WEBHOOK_SECRET, signature, body_str)) # True
require 'openssl'
def check_signature(secret_key, request_signature, request_body)
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.new(secret_key, digest)
hmac.update(request_body)
hmac.to_s == request_signature
end
# req_sig = request.headers['HTTP-HRFLOW-SIGNATURE']
# req_body = request.body.read
# secret_key = ENV['HRFLOW_WEBHOOK_KEY']
req_sig = '9d101d2bf630748679226b767d2031634c520390ff0e926afc09bc65a05bfdb2'
req_body = '4567'
secret_key = '1234'
puts check_signature(secret_key, req_sig, req_body)
2. Handling a Webhook Request with a Workflow CATCH
Handling an event is very simple. You can specify callback function to handle every event incoming on your webhooks' endpoint. Each event must have its own handling function.
Requierments
Requirements
- The
REQUEST
header- The
X-API-KEY
with Read & Write permissions- The
WEBHOOK SECRET KEY
- An endpoint receiving post requests
In the following example, we will use a Workflow CATCH URL
as an endpoint to catch Webhook events.
You will just have to:
- Go to Connections > Connectors Marketplace > Workflows > Catch and create a Workflow with the type
CATCH
. - Go to Connections > Connectors Marketplace > Destinations > Webhook > Settings and add a Webhook with an event type, an event name and copy/paste your
Workflow CATCH URL
The following example shows you how you can:
- Receive a Webhook Event
- Decode the Webook Event
- Verify the signature of the sender
- Execute a business logic
from hrflow import Hrflow
import mailchimp_transactional as MailchimpTransactional
from mailchimp_transactional.api_client import ApiClientError
def get_profile_event(_request: dict) -> dict:
"""
Reconstruct webhook body
@param _request: POST request
@return: webhook event
"""
return {
"type": _request.get("type"),
"origin": _request.get("type"),
"message": _request.get("message"),
"profile": _request.get("profile")
}
def verify_webhook(signature: str, event: dict, secret: str) -> null:
"""
Verify Webhook Signature
@param signature: webhook integrity signature
@param event: webhook data event
@param secret: webhook secret key
"""
import json
import hmac
import hashlib
message = json.dumps(event, separators=(",", ":")).encode()
hasher = hmac.new(secret.encode(), message, hashlib.sha256)
dig = hasher.hexdigest()
assert hmac.compare_digest(dig, signature)
def workflow(_request: dict, settings: dict) -> None:
"""
WORKFLOW to send an email to a profile after catching a wehook parsing success event
@rtype: null
@param _request: dictionary that contains the body and the headers of the request
@param settings: dictionary of settings params of the workflow
"""
if not _request.get("profile"):
return
webhook_event = get_profile_event(_request)
assert webhook_event["type"] == "profile.parsing.success" # This workflow is only to create a new profile
verify_webhook(_request["HTTP-HRFLOW-SIGNATURE"], webhook_event, settings["HRFLOW_WEBHOOK_SECRET"])
profile_key = webhook_event["profile"]["key"]
source_key = webhook_event["profile"]["source"]["key"]
# Retrieve Profile from HrFlow.ai
hrflow_client = Hrflow(api_secret=settings["HRFLOW_API_SECRET"], api_user=settings["HRFLOW_API_USER"])
profile = hrflow_client.profile.storing.get(source_key=source_key, key=profile_key).get("data")
assert profile is not None
# send Email with Mailchimp
mailchimp = MailchimpTransactional.Client(settings["MANDRILL_API_TOKEN"])
message = {
"from_email": settings["HRFLOW_API_USER"],
"subject": "Thank you for your application",
"text": "Your application is well received. Our team will reach out to you if there are any opportunities "
"matching your profile. Best, the Team",
"to": [
{
"email": profile["info"]["email"],
"type": "to"
}
]
}
try:
response = mailchimp.messages.send({"message": message})
print("AcPI called successfully: {}".format(response))
except ApiClientError as error:
print("An excception occurred: {}".format(error.text))
Updated 9 months ago