> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.superpath.io/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# 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](https://help.superpath.io/en/article/webhooks-get-updates-in-real-time-19fivqo/).

## 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:

```http
SuperPath-Signature: 1644921396,2029f1cbd5f55df4e19c5380e73492ff35be632a611160be435c69971c0606ab
```

The value has two comma-separated components:

| Component | Example | Description |
|---|---|---|
| Timestamp | `1644921396` | 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](https://en.wikipedia.org/wiki/Replay_attack). |
| Signature | `2029f1cb…0606ab` | A lowercase hex-encoded [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) using the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) 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

```http
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](https://help.superpath.io/en/article/the-event-object-hhq80t/).

## 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.

![](https://storage.crisp.chat/users/helpdesk/website/c0645e580ffc0800/image_ea2wfu.png)

**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:

```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:

```bash
printf '1644921396' | openssl dgst -sha256 -hmac 'YOUR_SECRET_KEY' -hex
```

## Things 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-Signature` header 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.
