Webhook signature verification, tested in your browser

Find out why webhook signature verification fails: paste the raw body, secret and header from HubSpot v3 or another app and compare.

Header X-HubSpot-Signature-v3 + X-HubSpot-Request-TimestampHMAC-SHA256, base64
Try:

Runs in your browser with Web Crypto. Nothing is sent or stored.

How HubSpot v3 signs
Header
X-HubSpot-Signature-v3 + X-HubSpot-Request-Timestamp
Algorithm
HMAC-SHA256, base64
Signed string
Method + URI + raw body + timestamp (X-HubSpot-Request-Timestamp), concatenated with nothing between
Timestamp
Reject if the timestamp is more than 5 minutes old
Key
The app's client secret
Watch for
Decode these URL-encoded characters in the URI before signing: %3A %2F %3F %40 %21 %24 %27 %28 %29 %2A %2C %3B (the bench does this for you). The question mark that starts the query string stays as it is. The URI is the full URL with https and host. The timestamp is in milliseconds.
Source
developers.hubspot.com: validating requests (opens in a new tab). Checked Nov 9, 2026
All 31 signature recipes
  • HubSpot v3HMAC-SHA256, base64
    Header
    X-HubSpot-Signature-v3 + X-HubSpot-Request-Timestamp
    Algorithm
    HMAC-SHA256, base64
    Signed string
    Method + URI + raw body + timestamp (X-HubSpot-Request-Timestamp), concatenated with nothing between
    Timestamp
    Reject if the timestamp is more than 5 minutes old
    Key
    The app's client secret
    Watch for
    Decode these URL-encoded characters in the URI before signing: %3A %2F %3F %40 %21 %24 %27 %28 %29 %2A %2C %3B (the bench does this for you). The question mark that starts the query string stays as it is. The URI is the full URL with https and host. The timestamp is in milliseconds.
    Source
    developers.hubspot.com: validating requests (opens in a new tab). Checked Nov 9, 2026
  • HubSpot v2SHA-256 of secret + method + URI + body, hex
    Header
    X-HubSpot-Signature + X-HubSpot-Signature-Version
    Algorithm
    SHA-256 of secret + method + URI + body, hex
    Signed string
    Client secret + HTTP method + URI + raw body, concatenated with nothing between
    Timestamp
    None documented
    Key
    The app's client secret
    Watch for
    Used for workflow webhook actions and custom CRM cards. X-HubSpot-Signature-Version reads v2. The URI must match the original request exactly, including the protocol. A GET has no body, so the string ends at the URI. It is a plain hash, not an HMAC.
    Source
    developers.hubspot.com: validating requests (opens in a new tab). Checked Nov 9, 2026
  • HubSpot v1SHA-256 of secret + body, hex
    Header
    X-HubSpot-Signature + X-HubSpot-Signature-Version
    Algorithm
    SHA-256 of secret + body, hex
    Signed string
    Client secret + raw body, concatenated with nothing between
    Timestamp
    None documented
    Key
    The app's client secret
    Watch for
    Used for CRM object event webhooks. X-HubSpot-Signature-Version reads v1. It is a plain hash, not an HMAC. It covers no time, so a captured request can be replayed.
    Source
    developers.hubspot.com: validating requests (opens in a new tab). Checked Nov 9, 2026
  • AttioHMAC-SHA256, hex
    Header
    Attio-Signature + X-Attio-Signature
    Algorithm
    HMAC-SHA256, hex
    Signed string
    Raw body, read as UTF-8
    Timestamp
    None documented
    Key
    The webhook's secret (developer settings, or the create response)
    Watch for
    X-Attio-Signature repeats the same value for legacy middleware. Only the body is signed, with no timestamp.
    Source
    docs.attio.com: webhooks (opens in a new tab). Checked Nov 9, 2026
  • CalendlyHMAC-SHA256, hex
    Header
    Calendly-Webhook-Signature
    Algorithm
    HMAC-SHA256, hex
    Signed string
    Timestamp (t) + '.' + body
    Timestamp
    The docs' examples reject a timestamp more than 3 minutes old
    Key
    The webhook signing key
    Watch for
    The header holds t= and v1= pairs. With a personal access token you can set the signing key when you create the subscription. For OAuth apps a key is generated for you.
    Source
    developer.calendly.com: webhook signatures (opens in a new tab). Checked Nov 9, 2026
  • ChargebeeBasic auth
    Header
    Authorization
    Algorithm
    Basic auth
    Signed string
    No signature. The request carries the HTTP Basic auth user and password set in webhook settings.
    Timestamp
    None documented
    Key
    The webhook's basic auth username and password
    Watch for
    Chargebee documents no HMAC signature. Turn on Protect webhook URL with basic authentication and set a username and password.
    Source
    chargebee.com: webhook settings (opens in a new tab). Checked Nov 9, 2026
  • Clerk SvixHMAC-SHA256, base64
    Header
    svix-signature + svix-id + svix-timestamp
    Algorithm
    HMAC-SHA256, base64
    Signed string
    svix-id + '.' + svix-timestamp + '.' + raw body
    Timestamp
    Compare the timestamp with your clock and reject it outside your tolerance
    Key
    The endpoint's signing secret (whsec_...)
    Watch for
    Clerk sends webhooks through Svix. The secret starts with whsec_; strip it and base64-decode the rest to get the key. svix-signature is a space-separated list such as v1,<signature>. Every retry of one event carries the same svix-id.
    Source
    clerk.com: webhooks overview (opens in a new tab), clerk.com: sync data (opens in a new tab), clerk.com: syncing (opens in a new tab), docs.svix.com: how manual (opens in a new tab). Checked Nov 9, 2026
  • CloseHMAC-SHA256, hex
    Header
    close-sig-hash + close-sig-timestamp
    Algorithm
    HMAC-SHA256, hex
    Signed string
    Timestamp (close-sig-timestamp) + raw body, concatenated with nothing between
    Timestamp
    None documented
    Key
    The subscription's signature_key, hex-decoded
    Watch for
    The signature_key is a hex string. Decode it from hex to bytes and use those bytes as the HMAC key. No tolerance window is documented.
    Source
    developer.close.com: webhooks (opens in a new tab). Checked Nov 9, 2026
Photo of Utku Zihnioglu

By

· CEO & co-founder

Updated Nov 11, 2026

Shorter page: the tool first, the answer in a few lines.

When webhook signature verification fails, check the bytes before you blame the secret. The sender signs the exact request it sent, and between its servers and your handler a framework can re-serialise the JSON, a proxy can rewrite the URL, or your code can read a timestamp in the wrong unit. Paste what your endpoint received, and the tool recomputes the signature, shows the string it signed and says whether the two agree.

It runs on your browser's own Web Crypto sign method. Nothing you paste is sent or saved, but a test secret is still the better habit.

Why the signature does not match

  1. The body changed. Many frameworks parse JSON before your handler sees it, and re-serialising it changes the hash. Read the raw bytes first.

  2. The URL is not the one the sender called. Behind a load balancer your app may see http or an internal host. HubSpot signs the public URL.

  3. Encoded characters. HubSpot v3 decodes a set of URL-encoded characters before signing.

  4. The timestamp unit. HubSpot sends milliseconds, while most apps that sign a timestamp send seconds.

  5. The wrong scheme or encoding. v3 is an HMAC in base64, while v1 and v2 are plain hashes in hex.

If all five check out, look at the secret last. HubSpot signs with the app's client secret, and an access token gives a clean, confident mismatch.

Verify in your own receiver

Compare signatures in constant time, with hmac.compare_digest in Python or crypto.timingSafeEqual in Node. Check the timestamp after the signature, against the window the sender documents. Accept more than one secret while you rotate, and deduplicate on the event's ID, since a retry is the same event.

Each scheme the tool checks cites the app maker's own docs. HubSpot's rest on HubSpot's guide to validating requests.

What to do next

If the signature matches here but fails in your receiver, your code's copy of the request is the problem: build the same signed string the tool shows.

For HubSpot, you can skip the receiver. In Oneprofile, turn on live changes for a table imported from HubSpot, and each delivery's signature is checked before it changes anything.

Webhook signature verification: common questions

What is webhook signature verification?

It is how your receiver proves a webhook came from the app that claims to send it and was not changed on the way. The sender signs each request with a secret you share, and you recompute that signature from the raw request and compare the two before you act on it.

What does HubSpot sign in a v3 webhook signature?

The HTTP method, the full request URL with https and the host, the raw body and the X-HubSpot-Request-Timestamp value, joined with nothing between. HubSpot keys an HMAC with your app's client secret and base64-encodes the result.

What is the difference between HubSpot v1, v2 and v3 signatures?

v1 and v2 are plain hashes of the client secret plus the request, with no time in them. v3 is an HMAC that also signs a timestamp, so a captured request stops verifying. HubSpot's docs name v1 for CRM object events and v2 for workflow actions and CRM cards.

Does every app sign its webhooks?

No. Some document only HTTP Basic auth on the webhook URL, and Google Drive echoes back a token you set on the channel. The tool checks those too, by comparing the Authorization header or the token instead of computing a signature.

About this article

Photo of Utku Zihnioglu

Utku Zihnioglu

CEO & co-founder

Serial entrepreneur who built infrastructure companies handling billions of requests. Now leads engineering at Oneprofile, which finds the people worth talking to, researches each one, and follows up until they reply.

Drafted with AI from our product docs and code. Utku Zihnioglu answers for what it says.

Get HubSpot changes, verified for you

Turn on live changes for a table imported from HubSpot, and each delivery is checked against its signature first.

Free plan: 200 credits a month, 3 integrations, no card

Get HubSpot changes, verified for you

Turn on live changes for a table imported from HubSpot, and each delivery is checked against its signature first.

Free plan: 200 credits a month, 3 integrations, no card