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

# Rotate Signing Secret

> Safely rotate your webhook signing secret with a 24-hour dual-signing grace window

## Overview

Ayrshare signs every webhook delivery with an [HMAC-SHA256](https://en.wikipedia.org/wiki/HMAC) of the payload, keyed by your **signing secret**, so your receiver can confirm a delivery genuinely came from Ayrshare. See [Webhook Security](https://www.ayrshare.com/docs/apis/webhooks/overview.md#webhook-security) for how verification works.

Rotating your signing secret lets you replace it on a regular schedule, or immediately if you suspect it has been exposed. To make rotation safe, Ayrshare opens a **24-hour grace window** after every rotation during which deliveries are signed with **both** your previous and your new secret. This lets you update your receiver on your own schedule without dropping or rejecting a single delivery — the same pattern used by Stripe and GitHub.

> [!NOTE]
>
> The signing secret is **profile-wide**: there is one secret per User Profile (UID),
> and it signs **every** webhook action that profile has registered. There is no
> per-action signing secret — setting or rotating the secret changes it for all
> actions on that profile at once.

## Rotate from the Dashboard

You can set or rotate your signing secret from the [Webhooks page](https://app.ayrshare.com/webhooks) in the Developer Dashboard. The **Signing Secret** panel appears above your webhook list once the profile has at least one registered webhook.

**1. Open the Signing Secret panel**

Go to the [Webhooks page](https://app.ayrshare.com/webhooks). If no secret is configured yet, the panel shows **No signing secret configured** with a **Set Signing Secret** button. If one is already configured, it shows **Signing secret configured** with a **Rotate** button.

**2. Set or Rotate**

Click **Set Signing Secret** (first-time) or **Rotate** (existing secret). A modal opens with a strong, randomly generated secret pre-filled and revealed. You can **Copy** it, **Regenerate** a new one, or toggle **paste my own** to supply your own value.

**3. Confirm**

Copy the secret somewhere safe — it is shown only once and can never be retrieved from the UI again — then confirm to submit. A success toast appears and the panel updates.

**4. Update your receiver**

On a rotation (not a first-time set), the panel shows an active grace-window indicator and the **Rotate** button is disabled until the window closes. You have 24 hours to deploy the new secret to your receiver.

## Rotate via the API

Rotate (or set) the signing secret with a single call. This creates a new secret, repoints the profile's secret reference to it, and — when an existing secret was in place — records the superseded secret as the previous secret with an expiry 24 hours out.

### Header Parameters

**Authorization** `string` *required*

[API Key](https://www.ayrshare.com/docs/apis/overview.md#authorization) of the Primary Profile.

Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`

[Profile Key](https://www.ayrshare.com/docs/apis/overview.md#profile-key-format) of a User Profile.

Format: `Profile-Key: PROFILE_KEY`

### Body Parameters

**secret** `string` *required*

The new signing secret value. Any non-empty string is accepted. We recommend a long, high-entropy random value (for example, 32 random bytes encoded as base64url).

```bash cURL theme={"system"}
curl --request POST \
  --url https://api.ayrshare.com/api/hook/webhook/secret \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Profile-Key: YOUR_PROFILE_KEY' \
  --data '{
    "secret": "your-new-signing-secret"
  }'
```

```json 200: Response theme={"system"}
{
  "status": "success",
  "action": "webhook",
  "refId": "3dc079614bdc3f281d9" // User Profile Ref Id
}
```

The plaintext `secret` is **never** returned in the response and is never logged. The response carries the client-facing `refId` (a hash of the UID), never the UID itself. The `Profile-Key` header is optional and scopes the rotation to a single User Profile for multi-profile accounts.

A missing or empty `secret` returns a mapped error (`code: 101`, "Missing/incorrect parameter") with an HTTP `400` status, and no change is made to your current secret. First-time set via the API (no existing secret) creates the secret with no previous secret recorded and no grace window.

## Safe Rotation Procedure

Because of the 24-hour grace window, there is no required order of operations — your receiver keeps working throughout. The recommended sequence is:

**1. Rotate the secret**

Rotate from the dashboard or via the API. Ayrshare immediately begins signing deliveries with both your previous and your new secret.

**2. Update your receiver**

Within 24 hours, deploy the new secret to your webhook receiver so it verifies against the new value.

**3. Let the window close**

After 24 hours, Ayrshare automatically clears the previous secret and signs only with the new secret. No further action is needed on your side.

> [!TIP]
>
> If you rotate again while a grace window is still open, the just-superseded
> secret becomes the new previous secret and a fresh 24-hour window starts. Only
> one previous secret is kept at a time.

## Verifying Signatures During the Grace Window

Outside of a grace window, signed deliveries carry the standard headers (see [Webhook Security](https://www.ayrshare.com/docs/apis/webhooks/overview.md#webhook-security)):

```bash theme={"system"}
X-Authorization-Timestamp : <Unix Timestamp In Seconds>
X-Authorization-Content-SHA256 : <current-sig>
X-Authorization-Content-SHA256-V2 : v1=<current-sig>
```

During the 24-hour window after a rotation, the new `X-Authorization-Content-SHA256-V2` header lists **both** signatures, current first, comma-separated:

```bash theme={"system"}
X-Authorization-Timestamp : <Unix Timestamp In Seconds>
X-Authorization-Content-SHA256 : <current-sig>
X-Authorization-Content-SHA256-V2 : v1=<current-sig>,v1=<previous-sig>
```

> [!NOTE]
>
> `X-Authorization-Content-SHA256` is unchanged: it always carries the single
> current-secret HMAC, for backward compatibility. Dual signatures appear only in
> the new `X-Authorization-Content-SHA256-V2` header.

Each value in `X-Authorization-Content-SHA256-V2` is prefixed with a scheme tag. `v1=` denotes an HMAC-SHA256 signature, computed exactly like `X-Authorization-Content-SHA256`. The `-V2` header is always present whenever a delivery is signed — it carries at least `v1=<current-sig>` — so you can rely on it as a stable receiver contract.

To verify a delivery during (or outside) a rotation:

**1. Compute the HMAC**

Compute the HMAC-SHA256 of the **raw request body** using your locally configured signing secret.

**2. Compare against every listed signature**

Read `X-Authorization-Content-SHA256-V2`, split it on commas, strip the `v1=` prefix from each value, and accept the delivery as authentic if your computed HMAC matches **any** listed `v1=` signature.

Accepting if **any** listed signature matches is what makes rotation zero-downtime: a receiver still configured with the old secret matches `v1=<previous-sig>`, while a receiver updated to the new secret matches `v1=<current-sig>` — both succeed throughout the window.

### Receiver Verification Example

```javascript Node.js theme={"system"}
import crypto from "crypto";

// secret is the signing secret currently configured on your receiver.
function isAuthenticWebhook(rawBody, headers, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody) // the raw, unparsed request body
    .digest("hex");

  const headerValue = headers["x-authorization-content-sha256-v2"] || "";

  // Accept if ANY v1= signature in the header matches our computed HMAC.
  return headerValue
    .split(",")
    .map((part) => part.trim())
    .filter((part) => part.startsWith("v1="))
    .map((part) => part.slice("v1=".length))
    .some((sig) => {
      const sigBuf = Buffer.from(sig);
      const expectedBuf = Buffer.from(expected);
      // timingSafeEqual throws on length mismatch — treat as not authentic.
      return (
        sigBuf.length === expectedBuf.length &&
        crypto.timingSafeEqual(sigBuf, expectedBuf)
      );
    });
}
```

> [!WARNING]
>
> Always compute the HMAC over the **raw** request body bytes, exactly as
> received — not over a re-serialized JSON object. Re-serialization can change
> whitespace or key order and break verification. Use a constant-time comparison
> (such as `crypto.timingSafeEqual`) to avoid timing attacks.

If a delivery's current secret record is missing, the delivery proceeds **unsigned** (no signature headers) rather than failing. If only the previous secret record is gone, the previous signature is skipped and the current signature is still emitted in both headers.


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