> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitelshield.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify webhook signatures

> Authenticate Orbitel Shield attack webhooks with HMAC-SHA256, validate the signed timestamp, and compare signatures against the unmodified request body.

Orbitel Shield signs attack alerts sent to webhook channels. Verify each signature before processing its payload so your integration can confirm the body was signed with the channel's secret.

## Find the signing secret

Find the channel's signing secret under **Protection > Alerts** in the panel. Use that secret for the corresponding channel. Keep it private and separate from your public API bearer key.

## Delivery headers

| Header | Supplied information |
| - | - |
| `X-Orbitel-Event` | Event header included with the delivery |
| `X-Orbitel-Delivery` | Delivery header included with the request |
| `X-Orbitel-Signature` | Signed timestamp and HMAC in the form `t=unix seconds,v1=hex digest` |

The supplied reference does not define event-name values, delivery-ID semantics, payload schemas, or retry schedules. Do not assume these from the header names alone.

## Signed content

The signature is the hexadecimal HMAC-SHA256 of the timestamp, a literal period, and the exact raw request body:

```text theme={null}
t.rawBody
```

Here `t` is the timestamp string from `X-Orbitel-Signature`. Calculate the HMAC using the channel signing secret. Reject requests older than **5 minutes**.

> [!WARNING]
>
> Preserve the raw request body before JSON parsing or other transformations. Parsing and re-serializing JSON can change whitespace or property order and invalidate the signature. Do not log the signing secret.

## Node.js verification example

This standalone example implements the supplied signing algorithm with input checks. It rejects malformed headers, future timestamps, signatures of the wrong length, and requests at or beyond the five-minute boundary before using a timing-safe comparison.

```javascript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyOrbitelWebhook({ header, rawBody, secret }) {
  if (
    typeof header !== "string" ||
    typeof secret !== "string" ||
    secret.length === 0 ||
    !Buffer.isBuffer(rawBody)
  ) {
    return false;
  }

  // Expect exactly one timestamp and one signature.
  const parts = header.split(",").map((part) => part.trim());
  if (parts.length !== 2) return false;

  const fields = {};
  for (const part of parts) {
    const separator = part.indexOf("=");
    if (separator < 1) return false;
    const name = part.slice(0, separator);
    const value = part.slice(separator + 1);
    if (name !== "t" && name !== "v1") return false;
    if (Object.hasOwn(fields, name)) return false;
    fields[name] = value;
  }

  const timestamp = fields.t;
  const signature = fields.v1;
  if (!/^\d+$/.test(timestamp ?? "")) return false;
  if (!/^[a-fA-F0-9]{64}$/.test(signature ?? "")) return false;

  const seconds = Number(timestamp);
  if (!Number.isSafeInteger(seconds)) return false;
  const age = Date.now() / 1000 - seconds;
  if (age < 0 || age >= 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.`, "utf8")
    .update(rawBody)
    .digest();
  const received = Buffer.from(signature, "hex");

  return (
    received.length === expected.length &&
    timingSafeEqual(received, expected)
  );
}
```

Pass the `X-Orbitel-Signature` value as `header`, the unmodified request bytes as `rawBody`, and the channel secret as `secret`. Only parse and process the JSON after verification returns `true`.

The future-timestamp rejection is a defensive check in this example. The supplied protocol explicitly requires rejecting requests older than five minutes. This example's exact five-minute cutoff follows the supplied `< 300` comparison.

## Troubleshooting verification

> [accordion] The HMAC does not match
>
> Check that the secret belongs to this channel and the input is the exact timestamp string followed by a period and the original body bytes. Do not verify a re-serialized JSON object.

> [accordion] The timestamp is rejected
>
> The timestamp is in Unix seconds. Check the receiver's clock and reject stale deliveries. The example also rejects timestamps in the future.

> [accordion] The signature has an invalid format
>
> Expect `t` and `v1` in the signature header. A SHA-256 digest in hexadecimal contains 64 hex characters. Validate the format and decoded length before timing-safe comparison.

## Related guides

> [card] [Analytics and alerts](https://docs.orbitelshield.com/operations/analytics.md)
>
> Understand alert channels, event selection, and attack investigation.

> [card] [API authentication](https://docs.orbitelshield.com/api-reference/overview.md)
>
> Use bearer API keys for outbound REST requests, separately from webhook signing secrets.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.