HackerEarth will sign the webhook events it sends to your endpoints by including a signature in each webhook request’s HE-Signature header. This allows you to verify that the events were sent by HackerEarth and not by a third party.
The HE-Signature header will contain a timestamp and one or more signatures. The timestamp is prefixed by t= and each signature is prefixed by a scheme. Schemes start with v, followed by an integer. Currently, the only valid signature scheme will be v1.
Following is an example of a signature header:
HE-Signature:
t=1492774577,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Note that newlines have been added for clarity, but a real *HE-Signature** header is on a single line.*
Before you can verify signatures, you need to obtain your webhooks secret key by reaching out to api@hackerearth.com and sharing your company account details and webhooks endpoint. HackerEarth generates a unique secret key for your company account's webhooks.
Once this setup is done and webhooks are enabled, HackerEarth starts to sign each webhook event it sends to your endpoint using this key.
Split the header, using the , (comma) character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair. The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature(s). You can discard all other elements.
You can achieve this by concatenating:
Compute an HMAC with the SHA256 hash function using the obtained webhooks secret key as the key and the signing_payload string as the message. Then obtain its hexdigest to get the expected signature.
Compare the signature(s) in the header to the expected signature. If a signature matches, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within the tolerance period (this is meant to prevent replay attacks).
The tolerance period that needs to be considered while verifying signatures would be 10 minutes.
Sample code snippet using python
#!/usr/bin/env python3
import hmac
import time
import hashlib
# Fetch data from request body
webhook_req_body = '{"webhook_event_id": "ce6c984d48c849e396f166f98b35cefe", "webhook_event_type": "CANDIDATE_REPORT_UPDATED", "webhook_attempt_number": 3, "webhook_payload": {"phone_number": "", "full_report_url": "", "start_datetime": "2021-01-21T01:24:05-06:00", "custom_details": {"Programming Language": "Python"}, "name": "Foo Bar", "institute": null, "unevaluated_submissions": true, "time_taken": "0:08:11", "problem_type_scores": {"Subjective": 0.0}, "status":"active", "score": 0.0, "candidate_assessment_report_url": "", "candidate_id": null, "graduation_year": null, "finish_datetime": "2021-01-21T01:32:16-06:00","full_report_pdf": "", "percentage": 0, "test_id": 123456, "email": "foo.bar@test.com", "questions_attempted": 0}}'
# Fetch HE-Signature:
t, v1 = he_signature_header.split(',')
t = t.split('=')[1]
v1 = v1.split('=')[1]
timestamp = str(int(time.time()))
signing_msg = '{}.{}'.format(t, webhook_req_body)
signing_key = '<secret key>' # use secret key provided
# Generate hmac signature to validate
signature = hmac.new(
signing_key.encode('utf-8'), msg=signing_msg.encode('utf-8'), digestmod=hashlib.sha256
).hexdigest()
# Compare signature with v1 fetched above
if v1==signature:
# Compare diff between timestamps recieved(t) and
# current timestamp(timestamp) should be less than 10 mins
return True
return False
For each Recruit Partner we have a config file (eg.):
{
"webhook_auth": false,
"webhook_auth_type": "Basic",
"webhook_username": "myusername",
"webhook_password": "mypassword"
}
For clients that need username/password authentication in the webhooks we have added Authorization header in the headers. If partner config data has the flag webhook_auth set as false, then the partner webhook URLs for this client will only accept requests if this header is set based on the following logic:
If webhook_auth_type is Basic, both webhook_username and webhook_password are required. The Authorization header will be formed like so:
base64_encoded_data = base64encode("myusername:mypassword")
headers["Authorization"] = f"Basic {base64_encoded_data}"
If webhook_auth_type is Bearer, only webhook_username will be used. The Authorization header will be formed like so:
headers["Authorization"] = "Bearer myusername"
NOTE: webhook_auth_type can take the value of Basic or Bearer.
Clients will be able to roll their secret key at HackerEarth platform. In such a case, HackerEarth will, right away, start using the newly generated secret key for signing webhooks.
The previous secret key will get deleted from HackerEarth systems after 24 hours, hence, till this duration both the keys would be active. HackerEarth systems will be generating one signature corresponding to each secret key and would be sending these signatures as separate v1 entries in the HE-Signature header. This will ease the rolling process without affecting the authentication flow at client’s end.
Following is an example of a signature header that would be sent in the scenarios when both the secret keys are active:
HE-Signature:
t=1492774577,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,
v1=5257aaaaa7ecebedabbbbbbbbfa51cad7e77a0e56ff4a7c8e6s08d8bd7q5a9d3
Note that newlines have been added for clarity, but a real *HE-Signature** header is on a single line.*
In case of any queries do reach us out at api@hackerearth.com