Verifying Webhook Signatures

For the reasons explained in Consuming Webhooks, Hook0 includes a signature with each sent webhook. This signature is a cryptographically secure hash of the payload, created using a secret key that only you and Hook0 know (it is associated with the subscription that sends the webhook). By comparing the signature included in the webhook with the one you compute yourself when receiving it, you can verify the authenticity of the webhook.

How It Works

  1. When we send a webhook, we generate a signature. This signature is computed by taking a hash (HMAC-SHA-256 in our case) of the event payload (and a few metadata), using a secret key that only you and Hook0 know.

  2. This signature is included in a X-Hook0-Signature header of the webhook.

  3. When you receive a webhook, before processing it, you should compute the signature on your side. To do this, see the next section.

  4. Then, you compare the signature you computed with the signature included with the webhook. If they match, you can be sure that the webhook is authentic and was sent by Hook0. If they don't match, you should reject the event.

  5. Now that your have established that the webhook you received was sent by Hook0 and was not altered, you should also compare the timestamp included in the X-Hook0-Signature header with the current timestamp and reject the webhook if it is too old. This helps mitigate replay attacks. A tolerance of 5 minutes is a good default value.

📘

Our SDKs can verify incoming webhooks for you!

You can use one of our SDKs to verify the signature of incoming webhooks so you don't have to implement the whole verification algorithm yourself.

Verifying Webhook Signatures v1 (recommended)

Here are the steps to verify webhook signatures:

Step 1: Extract the timestamp and signature from the header

First, split the X-Hook0-Signature header value using the , character as the separator to get a list of elements. Then split each element using the = character as the separator to get a key-value pair.

The t field corresponds to the timestamp, the h field to the list of headers included in the signature and the v1 field corresponds to the signature. You can ignore all other elements.

Step 2: Prepare the signed_payload string

Prepare a header_values string by:

  • Splitting the h field using the (space) character
  • Replacing each header name by its value, taken from the incoming HTTP request
  • Joining them (in the same order) using a . character as separator

Then, the signed_payload string is created by concatenating the following:

  • The timestamp (as a string)
  • The character .
  • The content of the h field
  • The character .
  • header_values
  • The character .
  • The actual payload (that is, the request body)

Step 3: Determine the expected signature

Compute a HMAC-SHA-256 signature. Use the subscription’s signing secret as the key (you can get it from Hook's API or UI), and the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature provided in the v1 field of the header to the signed_payload string. If the signatures match, compute the difference between the current timestamp and the received timestamp (t field). Decide if this difference falls within your acceptable limits.

To safeguard against timing attacks, use a constant-time string comparison to compare the computed and expected signatures.

Limitations and Considerations

  • The security of this method hinges on the secrecy of the secret key. If a malicious actor gains access to your secret key, they could create valid signatures and send fake events that would pass verification.

  • Supplement with other security measures:

    • use HTTPS for your subscription's target URL to secure data transmission
    • if you can, avoid transmitting personal or sensible information in the webhook's payload

🚧

About v0 and v1 signatures

We recently introduced v1 signatures in order to increase security (basically, include some other header values such as X-Event-Id and X-Event-Type in the signature). As such, h and v1 fields were introduced in the value of the X-Hook0-Signature header. v0 signatures are still provided in that same header to ensure compatibility:

  • old verification code will find t and v0 fields and ignore new fields; verification will continue to work
  • new verification code will find t, h and v1 fields and perform a better verification process while the v0 is ignored

We advice users to switch to the v1 signature verification process as it is more secure and more future-proof.

Verifying Webhook Signatures v0 (deprecated)

Here are the steps to verify webhook signatures:

Step 1: Extract the timestamp and signature from the header

First, split the X-Hook0-Signature header value using the , character as the separator to get a list of elements. Then split each element using the = character as the separator to get a key-value pair.

The t field corresponds to the timestamp and the v0 field corresponds to the signature. You can ignore all other elements.

Step 2: Prepare the signed_payload string

The signed_payload string is created by concatenating the following:

  • The timestamp (as a string)
  • The character .
  • The actual payload (that is, the request body)

Step 3: Determine the expected signature

Compute a HMAC-SHA-256 signature. Use the subscription’s signing secret as the key (you can get it from Hook's API or UI), and the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature provided in the v0 field of the header to the signed_payload string. If the signatures match, compute the difference between the current timestamp and the received timestamp (t field). Decide if this difference falls within your acceptable limits.

To safeguard against timing attacks, use a constant-time string comparison to compare the computed and expected signatures.