Verify Webhooks
Verify the signature and timestamp when processing webhooks
Because of the way webhooks work, attackers can impersonate services by simply sending a fake webhook to an endpoint. Think about it: it’s just an HTTP POST from an unknown source. This can create a potential security vulnerability for many applications, or at the very least, a source of errors.
To prevent this, Crossmint signs every webhook and its metadata with a unique key for each endpoint. Use this signature to verify that the webhook indeed comes from Crossmint, and process it only if it does.
You can get the signing key secret from the Webhooks page in the console. To find it, go to the endpoint details and look for the Signing Secret section.
We are going to use the Svix open-source library to verify webhooks. First, install the relevant libraries for your language:
Next, verify webhooks using the code below. The payload is the raw (string) body of the request, and the headers are the headers passed in the request.
You need to use the raw request body when verifying webhooks, as the cryptographic signature is sensitive to even the slightest changes. Watch out for frameworks that parse the request as JSON and then stringify it, as this will break the signature verification.
See examples below for how to get the raw request body with different frameworks.
Remember to get the signature secret from the endpoint details in the console.
We are going to use the Svix open-source library to verify webhooks. First, install the relevant libraries for your language:
Next, verify webhooks using the code below. The payload is the raw (string) body of the request, and the headers are the headers passed in the request.
You need to use the raw request body when verifying webhooks, as the cryptographic signature is sensitive to even the slightest changes. Watch out for frameworks that parse the request as JSON and then stringify it, as this will break the signature verification.
See examples below for how to get the raw request body with different frameworks.
Remember to get the signature secret from the endpoint details in the console.
This is a manual verification process. We recommend using a library to verify webhooks automatically.
If you need to verify manually, follow these instructions.
Each webhook call includes three headers with additional information that are used for verification:
svix-id
: The unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is resent (e.g., due to a previous failure).svix-timestamp
: Timestamp in seconds since epoch.svix-signature
: The Base64 encoded list of signatures (space delimited).
Constructing the Signed Content
The content to sign is composed by concatenating the ID, timestamp, and payload, separated by the full-stop character (.
). In code, it will look something like this:
Where body
is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should not change the body in any way before verifying.
Ensure you use the raw request body for verification. Parsing and re-stringifying JSON will alter the content and cause verification failure.
Determining the Expected Signature
We use HMAC with SHA-256 to sign webhooks.
To calculate the expected signature, HMAC the signedContent
from above using the base64 portion of your signing secret (this is the part after the whsec_
prefix) as the key. For example, if your secret is whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
, use MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
.
For example, this is how you can calculate the signature in Node.js:
This generated signature should match one of the ones sent in the svix-signature
header.
The svix-signature
header is a list of space-delimited signatures with version identifiers. The list is typically one item but can contain multiple signatures, like this:
Make sure to remove the version prefix and delimiter (e.g. v1,
) before verifying the signature.
Please note that to compare the signatures it’s recommended to use a constant-time string comparison method in order to prevent timing attacks.
Verify timestamp
We include the timestamp of the attempt in the svix-timestamp
header. Compare this timestamp against your system timestamp to ensure it’s within an acceptable tolerance to prevent replay attacks.
Was this page helpful?