x-signature HTTP header. You must verify this signature before processing the payload to confirm it was sent by Verisoul and has not been tampered with.
Signature Header Format
Thex-signature header contains three comma-separated components:
| Component | Description |
|---|---|
t | Unix timestamp (seconds) when the webhook was signed |
h | Header names included in the signature: content-type x-event-id x-event-type |
v1 | Hex-encoded HMAC-SHA256 signature |
How the Signature is Computed
The signature is an HMAC-SHA256 hash over a signed data string constructed by joining these values with. (dots):
t=1773933769h=content-type x-event-id x-event-typecontent-type: application/jsonx-event-id: 5ded1748-8c2f-4ef4-8276-32af793f62b0x-event-type: email.intelligence.completed- Body:
{"request_id":"ffd7df51-...","status":"error",...}
Verification Steps
Parse the signature header
Split the
x-signature header value on , and extract the t, h, and v1 components. Reject the request if any component is missing.Check the timestamp
Parse
t as a Unix timestamp and compare it to your server’s current time. Reject the request if the difference exceeds your tolerance (recommended: 5 minutes). This prevents replay attacks.Build the signed data string
Look up the value of each header named in
h (content-type, x-event-id, x-event-type), then concatenate with . separators:{t}.{h}.{content-type value}.{x-event-id value}.{x-event-type value}.{raw_body}Compute the expected signature
Compute
HMAC-SHA256 over the signed data string using your webhook subscription’s shared secret as the key. Hex-encode the result (lowercase).Code Examples
Test Your Implementation
You can generate a valid signature locally and send a test webhook withcurl:
Troubleshooting
Signature always fails to verify
Signature always fails to verify
The most common cause is using a parsed and re-serialized request body instead of the raw bytes. JSON serializers may reorder keys, change whitespace, or alter number formatting. Always verify against the raw body exactly as received.
Timestamp rejected but signature is correct
Timestamp rejected but signature is correct
Ensure your server’s clock is synchronized via NTP. The default tolerance is 5 minutes (300 seconds). If your server clock drifts beyond this, verification will fail even with a valid HMAC. You can increase the tolerance, but keep it as short as practical.
Wrong secret
Wrong secret
Each webhook subscription has its own secret. You can find it in the Verisoul dashboard under your webhook subscription settings. Make sure you are using the secret that corresponds to the subscription receiving the webhook.
Header name casing
Header name casing
The header names listed in the
h component (content-type, x-event-id, x-event-type) are lowercase. When looking up header values, make sure your framework’s header access is case-insensitive or that you normalize to lowercase.Using SHA1 instead of SHA256
Using SHA1 instead of SHA256
Verisoul uses HMAC-SHA256, not HMAC-SHA1. Double-check the hash algorithm in your implementation.
