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:

  1. Extract the versioned signature strings by splitting the value of the x-everee-webhook-signature header on the comma character (Unicode U+002C).

  2. 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 string v1.

  3. 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).

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

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

  1. 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, ",")
  1. 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)
  1. 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)
  1. Try to find a matching signature from the header:
authentic = false

for each signature in extracted_signatures:
	authentic = authentic || secure_compare(signature, computed_signature)
  1. 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.