Onfido logo home page
Get in touch
Arrow back Back to guides

Manual webhook signature verification

Webhook security

For API v3 users, we recommend you use the built-in support for webhook verification in our client libraries.

If you're unable to do so (for example, you're using API v2), you can follow the instructions on this page.

Extract the signature and event body

Each webhook event will have an X-SHA2-Signature header. This contains the webhook event’s signature in hexadecimal format.

Make sure you use the raw event request body. If you parse it from JSON first, the fields may be reordered.

Compute the expected signature

You can get the expected signature of the webhook by computing the HMAC of the event request body using the SHA256 algorithm, using the webhook’s secret token as the key.

Compare the signatures

A webhook event is valid if the signature from the header is equal to the expected signature you compute for it. Make sure to decode the signature from the header from hexadecimal format before comparing.

You should use a constant time equality function to prevent timing attacks. A timing attack is where a malicious user measures the small time differences taken to compare the signatures over many requests, to eventually work out the expected signature for a webhook event. A constant time equality function prevents this by always taking the same amount of time when comparing strings of a particular length.

Example pseudocode

You may find the following pseudocode useful:

def verify_webhook_event_signature(webhook_event_request)
  signature_header_hexidecimal = webhook_event_request.get_header("x-sha2-signature")

  # Make sure to use the raw event body and not to convert it to an object first.
  raw_event_body = webhook_event_request.body_as_string

  # Compute the HMAC using the SHA256 algorithm and using your webhook's token as the key.
  expected_signature = hmac(algorithm="SHA256", key=webhook_token, data=raw_event_body)

  # Make sure to decode the signature header from hexidecimal before comparing.
  # Alternatively, convert the expected signature to hexidecimal.
  signature_header = decode_hexadecimal(signature_header_hexidecimal)

  # Use a constant time equality function to prevent timing attacks.
  if constant_time_equals(signature_header, expected_signature)
    # It is now safe to parse the event body.
    return parse_json(raw_event_body)
    # Handle an invalid webhook event.
    error("Invalid webhook event")