Manually verify Straddle webhook signatures without an SDK, including the HMAC computation, header parsing, and timestamp checks for replay protection.
Using the official libraries is the recommended and most straightforward method to verify webhooks (as discussed in the previous verification guide). However, there may be situations where you need to verify them manually. This section explains the manual verification process in detail.
The recommended way to verify webhooks is using the official libraries. Consider that implementation strategy first.
Each webhook call includes three headers that are crucial for verifying authenticity:
webhook-id: A unique message identifier for the webhook message. This value is unique across all messages. If a webhook is resent (due to a previous failure), the webhook-id remains the same.
webhook-timestamp: The timestamp of the attempt in seconds since the Unix epoch.
webhook-signature: A Base64-encoded list of signatures (space-delimited).
You use these headers, along with the raw request body, to generate and compare the expected signature.
It’s critical that you use the raw request body here. Any modifications (such as re-stringifying a parsed JSON body) will change the content and result in a signature mismatch.
const crypto = require('crypto');const svix_id = "msg_p5jXN8AQM9LWM0D4loKWxJek";const svix_timestamp = "1614265330";const body = '{"test": 2432232314}';const secret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";const signedContent = `${svix_id}.${svix_timestamp}.${body}`;const secretBytes = Buffer.from(secret.split('_')[1], "base64");const signature = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64');console.log(signature);// This signature should match one of those found in the `webhook-signature` header.
Each signature is prefixed by a version identifier and a comma, such as v1,. To verify, remove the prefix (e.g., v1,) and compare the remaining Base64 string to your computed signature.
Use a constant-time comparison method to compare signatures and prevent timing attacks.
The webhook-timestamp header includes the timestamp of the attempt. Make sure this timestamp is close to your current time (within a certain allowed skew, like a few minutes) to prevent replay attacks.
Here is an example you can use to validate your manual verification logic. Note that this may fail verification due to an old timestamp, and that’s expected if you’re strictly enforcing freshness.
const secret = 'whsec_plJ3nmyCDGBKInavdOK15jsl';const payload = '{"event_type":"ping","data":{"success":true}}';const msg_id = 'msg_loFOjxBNrRLzqYUe';const timestamp = '1731705121';// The expected signature would be generated following the steps described above.// Replace with actual code to verify and compare the result.
You can also use the open-source Standard Webhooks simulation tool to generate as many examples as needed.