Authenticating Events
In order for clients to authenticate that a webhook event was sent from Everee and can be trusted, HTTP headers like the following examples are included on each request:
x-everee-webhook-timestamp: 1617756644
x-everee-webhook-signature: v1=939783e540[...],v1=77afec359d7[...]
The x-everee-webhook-signature
header may contain multiple signatures if there are multiple webhook signing keys active for a client account. Each signature is paired with a version identifier; currently the only valid version is v1
.
To verify that a webhook event is authentic:
-
Extract the versioned signature strings by splitting the value of the
x-everee-webhook-signature
header on the comma character (Unicode U+002C). -
Split each versioned signature string on the equals character (Unicode U+003D). For any signatures whose first part (the "version" part) is the string
v1
, retain the second part, which is the signature. Discard any signatures whose "version" part is not the stringv1
. -
Create the “message” by concatenating the timestamp in the
x-everee-webhook-timestamp
header, a period character (Unicode U+002E), and the raw JSON payload of the webhook event (the HTTP body). -
Compute an HMAC signature of that message using the SHA-256 cryptographic hash function and the webhook signing key provided by Everee. Ensure the signature is hex-encoded.
-
Compare the computed signature with each signature extracted from the header. If any header signature matches the signature you computed, the webhook is authentic.
In psuedocode
- Extract the list of versioned signatures from the header (
v1=939783e540...
):
signature_header = header_map.get("x-everee-webhook-signature")
extracted_signatures = split_string(signature_header, ",")
- Obtain valid signature values (
"939783e540..."
):
signatures = empty_list()
for each versioned_signature in extracted_signatures:
parts = split_string(versioned_signature, "=")
version = parts[0]
signature = parts[1]
if (version == "v1"):
list_append(signatures, signature)
- Compute the signature locally:
timestamp = header_map.get("x-everee-webhook-timestamp")
raw_request_body = request.get_body()
message = concat(timestamp, ".", raw_request_body)
computed_signature_bytes = hmac_sha256(webhook_signing_key, message)
computed_signature = hex_encode(computed_signature_bytes)
- Try to find a matching signature from the header:
authentic = false
for each signature in extracted_signatures:
authentic = authentic || secure_compare(signature, computed_signature)
- Reject the delivery if no matching signature is found:
if (!authentic):
send_http_response_code(401)
stop_processing_request()
If a matching signature is found, the message is authentic, and you can proceed to handle the webhook event and respond with a 2xx status code.
Updated over 1 year ago