Webhook Signature Authentication
Webhook Signature Authentication
In this article:
- Overview
- Turning on signature authentication
- The SuperPath-Signature header
- Example request
- How to verify the signature
- Things to keep in mind
- FAQs
Overview
SuperPath can optionally sign each webhook by including a signature in each request's header. This allows you to verify that the event requests were sent by SuperPath and not by an unauthorised third party.
Signature authentication is configured per webhook and is off by default. When it is on, SuperPath adds a SuperPath-Signature header to every request it sends to that webhook's endpoint. Your endpoint recomputes the same signature using the webhook's secret key and only trusts the request if the two match.
This article covers the header format and how to verify it in your own code. For where the setting lives in the app, see the help article How to Secure a Webhook with Signature Authentication. For webhook delivery in general, see Webhooks - Get updates in real-time.
Turning on signature authentication
Signature authentication is switched on when you create or edit a webhook, under Settings → Integrations → Manage webhooks. Turn on the Webhook signature auth toggle in the Add Webhook or Update Webhook window and you will be presented with a Secret key, with a copy button next to it. Copy that key and configure it on your receiving endpoint — it is the shared secret both sides use to compute the signature.
The key is only stored against the webhook when you click Add or Update. If you regenerate it later with the refresh button, you must update it on your endpoint at the same time or verification will start failing. The step-by-step version of all this, with screenshots, is in the help article How to Secure a Webhook with Signature Authentication.
The SuperPath-Signature header
With signature authentication on, every request to your endpoint carries this header:
SuperPath-Signature: 1644921396,2029f1cbd5f55df4e19c5380e73492ff35be632a611160be435c69971c0606abThe value has two comma-separated components:
Component | Example | Description |
|---|---|---|
Timestamp |
| The Unix timestamp, in seconds, at which the signature was created. Use it to reject requests whose timestamp is too far in the past and so guard against replay attacks. |
Signature |
| A lowercase hex-encoded HMAC using the SHA-256 algorithm, keyed with the webhook's secret key. |
The signed message is the timestamp string itself — the decimal seconds value, UTF-8 encoded. The JSON body is not part of the signed data, so a matching signature tells you the request came from a sender that knows your secret key; it does not on its own prove the body was not altered in transit. Always receive webhooks over HTTPS so the body is protected by TLS.
Example request
POST /webhooktest HTTP/1.1
Host: your-endpoint.example.com
Content-Type: application/json
SuperPath-Signature: 1644921396,2029f1cbd5f55df4e19c5380e73492ff35be632a611160be435c69971c0606ab
{"event":"user.updated","dateSent":"2022-02-15T10:36:36.300Z","dateSentOffset":0,"data":{"tenantId":"abcdef","id":"foettyQz714xBVi75gZ6","firstName":"John","lastName":"Lennon","email":"john@beatles.com","position":"Founder & CEO","roleId":"owner","roleName":"Owner","avatar":"https://res.cloudinary.com/superpath/image/upload/23423234234/john.jpg","username":"john.lennon","employeeId":"2123234","timezone":"Australia/Sydney","pointsTotal":427,"pointsSelfAssigned":336,"pointsAssigned":0,"status":"active","isDeleted":false,"dateCreated":"2020-11-13T11:48:42.109Z","dateCreatedOffset":11,"dateLastModified":"2022-02-15T10:36:21.354Z","dateLastModifiedOffset":11,"userFullNameLastModified":"Jake Jackson","userIdLastModified":"foettyQz714xBVi75gZ6"}}For the envelope around data, see The Event Object.
How to verify the signature
Step 1: Extract the timestamp and signature from the request header
Split the value of the SuperPath-Signature header on the , character. You should get an array of exactly two elements — the timestamp, then the signature.
Step 2: Calculate your own signature
Using the secret key you copied when you registered the webhook, and the timestamp you obtained in Step 1, compute an HMAC with the SHA-256 hash over the timestamp string and hex-encode the result.

Step 3: Compare the received signature with your own
Compare the signature you received in the request header with the one you just generated. If they match, the request comes from a sender who knows your secret key. Use a constant-time comparison rather than === so you do not leak information through timing.
Step 4: Reject stale timestamps
Check how old the timestamp is and reject anything outside a tolerance you are comfortable with — a few minutes is typical. This is what stops a captured request being replayed against your endpoint later.
Putting it together in Node.js:
const crypto = require('crypto');
// Choose your own tolerance; a few minutes is typical.
const MAX_AGE_SECONDS = 300;
function verifySuperPathSignature(headerValue, secretKey) {
if (!headerValue) return false;
// Step 1: split the header into its two components
const parts = headerValue.split(',');
if (parts.length !== 2) return false;
const [timestamp, receivedSignature] = parts;
// Step 2: recompute the signature over the timestamp using your secret key
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(Buffer.from(timestamp, 'utf-8'))
.digest('hex');
// Step 3: compare in constant time
const received = Buffer.from(receivedSignature, 'utf-8');
const expected = Buffer.from(expectedSignature, 'utf-8');
if (received.length !== expected.length) return false;
if (!crypto.timingSafeEqual(received, expected)) return false;
// Step 4: reject stale timestamps
const ageSeconds = Math.floor(Date.now() / 1000) - Number(timestamp);
return ageSeconds >= 0 && ageSeconds <= MAX_AGE_SECONDS;
}
app.post('/superpath-webhook', express.json(), (req, res) => {
const signatureHeader = req.get('SuperPath-Signature');
if (!verifySuperPathSignature(signatureHeader, process.env.SUPERPATH_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
res.sendStatus(200);
// ...then process req.body
});
If you want to check your implementation from the command line, this produces the signature for a given timestamp and secret — it should match the second component of the header for that timestamp:
printf '1644921396' | openssl dgst -sha256 -hmac 'YOUR_SECRET_KEY' -hexThings to keep in mind
- Signature authentication is optional and off by default. It is set per webhook, so turning it on for one webhook does not affect the others.
- SuperPath signs the request, but only your endpoint can enforce it. Until you actually verify the header and reject requests that fail, the setting adds no protection.
- The signed message is the timestamp, not the body. Verifying the signature authenticates the sender, not the contents of the payload — rely on HTTPS for body integrity.
- The signature changes on every request even when the payload is identical, because the timestamp changes. Do not cache or compare signatures between deliveries.
- Regenerating the secret key invalidates the old one as soon as you save the webhook. Update your endpoint at the same time to avoid rejecting live traffic.
- Turning the toggle off removes the secret key from the webhook, so requests stop being signed and your endpoint will see no
SuperPath-Signatureheader at all. - Store the secret key the way you would store an API key — server-side, out of source control.
- A rejected request is not retried. SuperPath does not re-send a delivery your endpoint refused, so make sure your verification is right before you start rejecting.
FAQs
What exactly is signed?
The Unix timestamp, in seconds, that appears as the first component of the header — UTF-8 encoded, HMAC-SHA256, keyed with your webhook secret, hex-encoded. The request body is not included.
Where do I get the secret key?
It appears in the Add Webhook or Update Webhook window as soon as you turn on Webhook signature auth. Copy it there — see the help article How to Secure a Webhook with Signature Authentication.
Is the header name case-sensitive?
HTTP header names are case-insensitive, so read it however your framework normalises headers. SuperPath sends it as SuperPath-Signature.
What should I return if verification fails?
Whatever suits your stack — a 401 is conventional. Bear in mind SuperPath treats any non-2xx response as a failed delivery and will not re-send it.
Can I use the same secret key for several webhooks?
Each webhook gets its own key when you turn the toggle on, and that is the safer arrangement. If your endpoint serves more than one webhook, keep the keys separate and work out which to use from the endpoint path.
Updated on: 25/07/2026
Thank you!
