For the complete documentation index, see llms.txt. This page is also available as Markdown.

How to set up authorization on a webhook

A guide to setting up authorization on a webhook.

How to set up OAuth 2.0 authorization for a webhook

OAuth 2.0 authorization lets your webhook obtain and use an access token for services that require one.

Before configuring OAuth 2.0 authorization, make sure you have the information required to obtain an access token from the external service:

  • Client ID: Identifies the client requesting the access token

  • Client Secret: Used with the Client ID to request an access token

  • Token URL: The endpoint used to request access tokens

  • Scope (optional): Defines the permissions granted to the access token

To configure OAuth 2.0 authorization, go to Settings > Integration > Webhooks.

Webhook configuration page

Open an existing webhook or create a new one.

Webhook configurations setup page

In the webhook configuration page, locate the authorization section and select "OAuth 2.0".

Enter the previously mentioned required information into the corresponding fields.

Next, choose how the client credentials should be sent when requesting an access token:

  • Basic Auth Header: Sends the "Client ID" and "Client Secret" in the authorization header

  • Request Body: Sends the "Client ID" and "Client Secret" as parameters in the request body

If the external service requires additional parameter you can add them using "Add parameter".

You can also configure custom headers for the webhook request under "Request headers".

Once you have entered the required settings, save the webhook configuration.

What happens when the webhook is triggered

When the webhook is triggered, the system first requests an access token from the configured Token URL using the provided client credentials.

Example of webhook with OAuth2 sent to token endpoint

If the credentials are valid, an access token is returned.

The system then automatically includes the token in the Authorization header and sends the webhook request to the configured endpoint.

Example of token returned

How to set up HMAC authorization for a webhook

When using HMAC authorization for webhooks, Struct signs the outbound HTTP requests it sends to your application so that you can verify that a request genuinely came from Struct and that the body was not tampered with in transit. The signature also carries a timestamp so you can reject replayed requests.

This documentation describes exactly how the signature is produced (by RequestSignatureService.GenerateSignature) so that your receiving application can recompute the same value and compare it against the one we send.

To get started we first need to enable HMAC authorization. To do this, we navigate to Settings > Integration > Webhooks and open an existing webhook or create a new one.

Webhook configuration page

In the Authorization section, select HMAC as the authorization method.

Webhook configurations setup page

Enter or generate a secret that will be used to sign outgoing webhook requests. The same shared secret must also be configured in the receiving system so it can verify the signature.

For security, use a strong random value of at least 32 characters for the secret.

The generated signature is included in the header of the webhook request.

Example of HMAC webhook with signature in header based on secret

The receiving system uses its copy of the shared secret to calculate the expected signature and compares it to the signature included in the request. If the signatures match, the request is considered authentic.

What happens when the webhook is triggered

When the webhook is triggered, Struct generates an HMAC-SHA256 signature using the configured shared secret.

The signature is generated using the following algorithm:

  • MAC: HMAC with SHA-256.

  • Key: the shared secret, encoded as UTF-8 bytes.

  • Message: the Unix timestamp, a literal . (period), then the raw request body, the whole string encoded as UTF-8 bytes.

  • Output: the raw HMAC digest (32 bytes) encoded as standard Base64 (+ / / alphabet, with = padding — not base64url, not hex).

The generated signature is included in the X-Hook-Signature header, and the timestamp used to generate the signature is included in the X-Hook-Timestamp header. The shared secret is never transmitted as part of the request.

With HMAC authorization enabled, Struct sends the webhook payload together with the following headers:

Header
Description

X-Hook-Timestamp

The Unix timestamp (UTC, in seconds) used to generate the signature.

X-Hook-Signature

The Base64-encoded HMAC-SHA256 signature.

Verifying the Signature

To verify the webhook request:

  1. Read the raw request body exactly as received, before parsing or modifying it.

  2. Read the X-Hook-Timestamp header.

  3. Reject stale requests: Check the age of the requests through abs(now − timestamp) in seconds and reject if it exceeds your tolerance. Struct itself validates with a 300-second (5-minute) window, so 300 s is the recommended value.

  4. Rebuild the signed message by concatenating the timestamp, a period (.), and the raw body string:

  5. Generate an HMAC-SHA256 signature through the algorithm Base64(HMAC_SHA256(clientSecret, message)).

  6. Use constant-time comparison: Compare the two signatures with a length-constant, value-constant comparison (e.g. crypto.timingSafeEqual, hmac.compare_digest, CryptographicOperations.FixedTimeEquals) rather than ==, to avoid leaking information through timing. This mirrors what Struct does when it validates a signature.

If the generated signature matches the received signature and the timestamp is within the accepted time window, the request can be considered authentic and the request body has not been modified.

Common Pitfalls

  • Always verify the raw request body. Deserializing and re-serializing the JSON can result in the key ordering, whitespace, Unicode escaping, and number formatting to all change, and the HMAC will not match. Always sign/verify the raw request body.

  • The timestamp is expressed in seconds, not milliseconds.

  • Use a constant-time comparison when comparing signatures to avoid timing attacks.

  • If HMAC authorization is enabled and the signature headers are missing, reject the request.

Validate your implementation

Use this known-good vector to validate your implementation. All three inputs are fed in verbatim; the body has no surrounding whitespace.

Input
Value

ClientSecret

stss_example_secret_do_not_use

Timestamp

1700000000

Body

{"appConnectionUid":"a1b2c3","tenantSlug":"acme"}

Signed message

1700000000.{"appConnectionUid":"a1b2c3","tenantSlug":"acme"}

Expected signature

n6Kw56YP7SFCqyyJAt5YP/Xg/98xbfyrRNmVPtOEFiM=

Examples of implementation

Below you can see various examples of implementation across different languages that can be used as reference for your implementation.

Node.js / TypeScript

Make sure you capture the raw body. With express.json() use the verify hook (express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } })) and verify against req.rawBody.toString("utf8"), not JSON.stringify(req.body).

C#

Last updated