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

# Rotar Signing Secret

> Rota de forma segura tu signing secret de webhook con una ventana de gracia de doble firma de 24 horas

export const PlansAvailable = ({plans = [], maxPackRequired}) => {
  let displayPlans = plans;
  if (plans && plans.length === 1) {
    const lowerCasePlan = plans[0].toLowerCase();
    if (lowerCasePlan === "business") {
      displayPlans = ["Launch", "Business", "Enterprise"];
    } else if (lowerCasePlan === "premium") {
      displayPlans = ["Premium", "Launch", "Business", "Enterprise"];
    }
  }
  return <Note>
Available on {displayPlans.length === 1 ? "the " : ""}
{displayPlans.join(", ").replace(/\b\w/g, l => l.toUpperCase())}{" "}
{displayPlans.length > 1 ? "plans" : "plan"}.

{maxPackRequired && <span onClick={() => window.open('https://www.ayrshare.com/docs/additional/maxpack', '_self')} className="flex items-center mt-2 cursor-pointer">
 <span className="px-1.5 py-0.5 rounded text-sm" style={{
    backgroundColor: '#C264B6',
    color: 'white',
    fontSize: '12px'
  }}>
   Max Pack required
 </span>
</span>}
</Note>;
};

export const HeaderAPI = ({noProfileKey, profileKeyRequired}) => <>
    <ParamField header="Authorization" type="string" required>
      <a href="/docs/apis/overview#authorization">API Key</a> of the Primary Profile.
      <br />
      <br />
      Format: <code>Authorization: Bearer API_KEY</code>
    </ParamField>
    {!noProfileKey && (profileKeyRequired ? <ParamField header="Profile-Key" type="string" required>
          <a href="/docs/apis/overview#profile-key-format">Profile Key</a> of a User Profile.
          <br />
          <br />
          Format: <code>Profile-Key: PROFILE_KEY</code>
        </ParamField> : <ParamField header="Profile-Key" type="string">
          <a href="/docs/apis/overview#profile-key-format">Profile Key</a> of a User Profile.
          <br />
          <br />
          Format: <code>Profile-Key: PROFILE_KEY</code>
        </ParamField>)}
  </>;

<PlansAvailable plans={["premium"]} maxPackRequired={false} />

## Visión general

Ayrshare firma cada entrega de webhook con un [HMAC-SHA256](https://en.wikipedia.org/wiki/HMAC) del payload, usando como clave tu **signing secret**, para que tu receptor pueda confirmar que la entrega procede realmente de Ayrshare. Consulta [Seguridad de webhook](/docs/apis/webhooks/overview#webhook-security) para ver cómo funciona la verificación.

Rotar tu signing secret te permite reemplazarlo de forma regular, o inmediatamente si sospechas que ha quedado expuesto. Para que la rotación sea segura, Ayrshare abre una **ventana de gracia de 24 horas** después de cada rotación durante la cual las entregas se firman con **ambos** secrets, el anterior y el nuevo. Esto te permite actualizar tu receptor a tu propio ritmo sin descartar ni rechazar una sola entrega — el mismo patrón que usan Stripe y GitHub.

<Note>
  El signing secret es **a nivel de perfil**: hay un secret por User Profile (UID),
  y firma **todas** las acciones de webhook que ese perfil tenga registradas. No existe un
  signing secret por acción — establecer o rotar el secret lo cambia para todas
  las acciones de ese perfil a la vez.
</Note>

## Rotar desde el Dashboard

Puedes establecer o rotar tu signing secret desde la [página Webhooks](https://app.ayrshare.com/webhooks) del Developer Dashboard. El panel **Signing Secret** aparece encima de tu lista de webhooks una vez que el perfil tiene al menos un webhook registrado.

<Steps>
  <Step title="Abre el panel Signing Secret">
    Ve a la [página Webhooks](https://app.ayrshare.com/webhooks). Si aún no hay ningún secret configurado, el panel muestra **No signing secret configured** con un botón **Set Signing Secret**. Si ya hay uno configurado, muestra **Signing secret configured** con un botón **Rotate**.
  </Step>

  <Step title="Set o Rotate">
    Haz clic en **Set Signing Secret** (por primera vez) o en **Rotate** (secret existente). Se abre un modal con un secret fuerte generado aleatoriamente ya rellenado y visible. Puedes hacer **Copy**, **Regenerate** para generar uno nuevo, o activar **paste my own** para introducir tu propio valor.
  </Step>

  <Step title="Confirma">
    Copia el secret en un lugar seguro — se muestra solo una vez y nunca podrá recuperarse desde la UI — y confirma para enviarlo. Aparece un toast de éxito y el panel se actualiza.
  </Step>

  <Step title="Actualiza tu receptor">
    En una rotación (no en una configuración inicial), el panel muestra un indicador de ventana de gracia activa y el botón **Rotate** queda deshabilitado hasta que la ventana se cierre. Tienes 24 horas para desplegar el nuevo secret en tu receptor.
  </Step>
</Steps>

## Rotar mediante la API

Rota (o establece) el signing secret con una única llamada. Esto crea un nuevo secret, reapunta la referencia del secret del perfil a él y — cuando ya existía un secret — registra el secret sustituido como el secret anterior con una expiración a 24 horas vista.

### Parámetros de cabecera

<HeaderAPI />

### Parámetros del cuerpo

<ParamField body="secret" type="string" required>
  El nuevo valor del signing secret. Se acepta cualquier string no vacío. Recomendamos un valor aleatorio largo y de alta entropía (por ejemplo, 32 bytes aleatorios codificados en base64url).
</ParamField>

<RequestExample>
  ```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"
    }'
  ```
</RequestExample>

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

El `secret` en texto plano **nunca** se devuelve en la respuesta y nunca se registra en logs. La respuesta contiene el `refId` orientado al cliente (un hash del UID), nunca el UID en sí. La cabecera `Profile-Key` es opcional y limita la rotación a un único User Profile para cuentas multiperfil.

Un `secret` faltante o vacío devuelve un error mapeado (`code: 101`, "Missing/incorrect parameter") con un status HTTP `400`, y no se realiza ningún cambio en tu secret actual. La configuración inicial mediante la API (sin secret existente) crea el secret sin registrar un secret anterior y sin ventana de gracia.

## Procedimiento seguro de rotación

Gracias a la ventana de gracia de 24 horas, no hay un orden de operaciones obligatorio — tu receptor sigue funcionando durante todo el proceso. La secuencia recomendada es:

<Steps>
  <Step title="Rota el secret">
    Rota desde el dashboard o mediante la API. Ayrshare comienza inmediatamente a firmar las entregas con ambos secrets, el anterior y el nuevo.
  </Step>

  <Step title="Actualiza tu receptor">
    En un plazo de 24 horas, despliega el nuevo secret en tu receptor de webhook para que verifique con el nuevo valor.
  </Step>

  <Step title="Deja que la ventana se cierre">
    Después de 24 horas, Ayrshare limpia automáticamente el secret anterior y firma solo con el nuevo secret. No es necesaria ninguna otra acción por tu parte.
  </Step>
</Steps>

<Tip>
  Si rotas de nuevo mientras hay una ventana de gracia aún abierta, el secret
  recién sustituido se convierte en el nuevo secret anterior y se inicia una
  nueva ventana de 24 horas. Solo se guarda un secret anterior a la vez.
</Tip>

## Verificación de firmas durante la ventana de gracia

Fuera de una ventana de gracia, las entregas firmadas llevan las cabeceras estándar (consulta [Seguridad de webhook](/docs/apis/webhooks/overview#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>
```

Durante la ventana de 24 horas posterior a una rotación, la nueva cabecera `X-Authorization-Content-SHA256-V2` lista **ambas** firmas, la actual primero, separadas por coma:

```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` no cambia: siempre lleva el único HMAC del
  secret actual, por compatibilidad hacia atrás. Las firmas duales solo
  aparecen en la nueva cabecera `X-Authorization-Content-SHA256-V2`.
</Note>

Cada valor en `X-Authorization-Content-SHA256-V2` va precedido por una etiqueta de esquema. `v1=` denota una firma HMAC-SHA256, calculada exactamente igual que `X-Authorization-Content-SHA256`. La cabecera `-V2` siempre está presente cuando una entrega está firmada — lleva como mínimo `v1=<current-sig>` — por lo que puedes confiar en ella como un contrato estable del receptor.

Para verificar una entrega durante (o fuera de) una rotación:

<Steps>
  <Step title="Calcula el HMAC">
    Calcula el HMAC-SHA256 del **cuerpo raw de la solicitud** usando tu signing secret configurado localmente.
  </Step>

  <Step title="Compara con todas las firmas listadas">
    Lee `X-Authorization-Content-SHA256-V2`, divídelo por comas, quita el prefijo `v1=` de cada valor y acepta la entrega como auténtica si tu HMAC calculado coincide con **cualquier** firma `v1=` listada.
  </Step>
</Steps>

Aceptar si **cualquier** firma listada coincide es lo que hace que la rotación sea sin downtime: un receptor todavía configurado con el secret antiguo coincide con `v1=<previous-sig>`, mientras que un receptor actualizado al nuevo secret coincide con `v1=<current-sig>` — ambos tienen éxito durante toda la ventana.

### Ejemplo de verificación en el receptor

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

// secret es el signing secret actualmente configurado en tu receptor.
function isAuthenticWebhook(rawBody, headers, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody) // el cuerpo raw sin parsear de la solicitud
    .digest("hex");

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

  // Aceptar si CUALQUIER firma v1= de la cabecera coincide con nuestro HMAC calculado.
  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 lanza excepción si las longitudes no coinciden — tratarlo como no auténtico.
      return (
        sigBuf.length === expectedBuf.length &&
        crypto.timingSafeEqual(sigBuf, expectedBuf)
      );
    });
}
```

<Warning>
  Calcula siempre el HMAC sobre los bytes **raw** del cuerpo de la solicitud,
  exactamente como se recibieron — no sobre un objeto JSON re-serializado. La
  re-serialización puede cambiar los espacios en blanco o el orden de las
  claves y romper la verificación. Usa una comparación en tiempo constante
  (como `crypto.timingSafeEqual`) para evitar timing attacks.
</Warning>

Si falta el registro del secret actual de una entrega, la entrega procede **sin firma** (sin cabeceras de firma) en lugar de fallar. Si solo falta el registro del secret anterior, la firma anterior se omite y la firma actual se sigue emitiendo en ambas cabeceras.
