Checking Webhook Signatures

Prefinery signs the webhook events it sends to your endpoints by including a signature in each event's X-Prefinery-Signature header. This allows you to verify that the events were sent by Prefinery, not by a third party.

Before you can verify the signature, you need to retrieve your project's webhook signing secret from inside your account on the Integrations > Webhooks page. Click the View secret button in order to display your project's unique signing secret.

Verifying the signature

The X-Prefinery-Signature header included in each signed event contains a timestamp and a signature. The timestamp is prefixed by t=, and the signature is prefixed by a scheme. Schemes start with v, followed by an integer. Currently, the only valid live signature scheme is v1.

      X-Prefinery-Signature: t=1612540400,v1=4c3cec7b9ef50db2ca9651701452b5a826a9fe96c0b34ad8eb56d5bbbad191d1

Prefinery generates signatures using a hash-based message authentication code (HMAC) with SHA-256. To prevent downgrade attacks, you should ignore all schemes that are not v1.

Step 1: Extract the timestamp and signatures from the header

Split the header, 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 prefix and value pair.

The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature. You can discard all other elements.

Step 2: Prepare the signed_payload string

The signed_payload string is created by concatenating:

  • The timestamp
  • The character .
  • The actual JSON payload (i.e., the request body)

Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use the project's signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, use a constant-time string comparison to compare the expected signature to each of the received signatures.

Preventing replay attacks

A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, Prefinery includes a timestamp in the X-Prefinery-Signature header. Because this timestamp is part of the signed payload, it is also verified by the signature, so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is too old, you can have your application reject the payload.

Your tolerance for what it means to be too old is up to you. We recommend using Network Time Protocol (NTP) to ensure that your server's clock is accurate and synchronizes with the time on Prefinery's servers.

Prefinery generates the timestamp and signature each time an event is sent to your endpoint. If Prefinery retries an event (e.g., your endpoint previously replied with a non-200 status code), then a new signature and timestamp is generated for the new delivery attempt.