Rota el secreto de un endpoint cuando se filtró (o sospechas que se filtró), cuando una persona con acceso deja el equipo, cuando lo perdiste, o de forma periódica según tu política de seguridad. La rotación genera un secreto nuevo y lo muestra una sola vez, igual que al crear el endpoint.

El secreto anterior deja de valer al instante

No hay una ventana en la que Compense firme con los dos. Desde el momento en que llamas a

POST/v1/gateway/webhooks/endpoints/{endpoint_id}/rotate-secret/ , toda entrega sale firmada con el secreto nuevo. Si tu receptor todavía no lo conoce, rechazará esas entregas con 401 hasta que lo despliegues.

La estrategia: verificar contra una lista #

Todas las funciones de verificación de los SDK aceptan varios secretos y dan la entrega por buena si coincide con cualquiera (comparando contra todos, sin cortocircuito, para no filtrar cuál coincidió). Mientras rotas, pasa [nuevo, viejo]:

Verificar contra el secreto nuevo y el anterior
from compense_gateway.webhooks import verify_webhook

event = verify_webhook(crudo, headers, secret=[SECRETO_NUEVO, SECRETO_VIEJO])

Los secretos vacíos de la lista se ignoran, así que puedes leer el anterior de una variable que casi siempre está vacía (COMPENSE_WEBHOOK_SECRET_OLD) sin tocar el código. Si la lista queda sin ningún secreto, los SDK lo tratan como error de configuración (no como firma inválida): ver la tabla de errores.

Paso a paso #

sequenceDiagram
    autonumber
    participant T as Tu equipo
    participant G as Gateway Compense
    participant V as Gestor de secretos
    participant R as Tu receptor
    T->>R: Despliega el receptor que acepta [vigente, anterior]
    T->>G: rotate-secret
    G-->>T: data.secret = whsec_nuevo (una sola vez)
    T->>V: vigente = nuevo, anterior = viejo
    V-->>R: El receptor recarga los secretos
    Note over G,R: Las entregas que fallaron en el intervalo se reintentan solas
    T->>G: Revisa el historial (deliveries status=failed)
    T->>V: Cuando todo verifique con el nuevo, borra el anterior
  1. Prepara el receptor. Despliega (antes de rotar) un receptor que lea dos secretos, el vigente y el anterior, de tu gestor de secretos o de variables de entorno, y que verifique contra ambos. Si puede recargarlos sin reiniciar, mejor.
  2. Rota. Llama a rotate_secret; la respuesta trae el secreto nuevo en data.secret.
  3. Guarda el nuevo de inmediato como vigente y mueve el actual a “anterior”. Hazlo en el mismo proceso que rota, para acortar el intervalo en que el receptor no conoce el nuevo.
  4. Haz que el receptor lo cargue (recarga en caliente o un despliegue). Durante un despliegue gradual, unas instancias tendrán la lista nueva y otras la vieja: por eso la lista existe.
  5. Revisa las entregas. Las que fallaron con 401 en el intervalo se reintentan solas (el primer reintento sale a los 30 segundos); si alguna quedó failed o abandoned, reintenta a mano.
  6. Retira el anterior cuando el historial muestre entregas exitosas con el nuevo. Deja la lista con un solo secreto hasta la próxima rotación.

Rotar por API #

import os
from compense_gateway import CompenseClient

client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")

tx = client.webhooks.endpoints.rotate_secret(
    "3f1a2b3c-0000-4a1b-9c2d-000000000001",
    idempotency_key="nova-wh-erp-001-rota-2026-11",
)
nuevo = tx.data["secret"]          # una sola vez: guárdalo YA como "vigente"
print(tx.data["id"], tx.data["rotated_at"])

Respuesta:

JSON
{
  "status": "success",
  "transaction_id": "a1b2c3d4-0000-4a1b-9c2d-000000000020",
  "module": "webhooks",
  "data": {
    "id": "3f1a2b3c-0000-4a1b-9c2d-000000000001",
    "secret": "whsec_...",
    "rotated_at": "2026-11-02T14:00:00Z"
  }
}

Repetir la rotación con la misma Idempotency-Key no devuelve el secreto

Si la respuesta se perdió y reintentas con la misma Idempotency-Key, el gateway devuelve la respuesta original con "idempotent_replay": true pero sin secret (no lo guarda). En ese caso el secreto que se generó es irrecuperable: rota otra vez con una llave nueva.

La CLI compense no tiene orden para rotar: usa el SDK o cURL.

Configuración típica del receptor #

Guarda dos entradas en tu gestor de secretos o en variables de entorno, y lee ambas:

VariableContenidoCuándo tiene valor
COMPENSE_WEBHOOK_SECRETSecreto vigente (whsec_...)Siempre
COMPENSE_WEBHOOK_SECRET_OLDSecreto anteriorSolo durante una rotación

En ASP.NET Core la convención de los ejemplos del SDK es una lista en configuración (Compense:WebhookSecrets:0 y Compense:WebhookSecrets:1, que como variables de entorno se escriben Compense__WebhookSecrets__0 y Compense__WebhookSecrets__1); en Laravel, un arreglo en config/services.php ('webhook_secrets' => array_values(array_filter([...]))). Los receptores completos están en Verificar la firma.

Si perdiste el secreto #

El gateway no guarda los secretos, así que no hay forma de recuperarlo: ninguna lectura lo devuelve y el replay idempotente de crear o rotar tampoco. La única salida es rotarlo:

  1. Rota el secreto (arriba) y guarda el nuevo.
  2. Configura el receptor solo con el nuevo (el perdido no te sirve para nada) y despliega.
  3. Las entregas que fallaron mientras tanto se reintentan solas; las que quedaron failed o abandoned las reprocesas en bloque.

Si el secreto se filtró #

Rota de inmediato, aunque eso te cueste algunos 401 mientras despliegas: un secreto filtrado permite a cualquiera enviarle a tu receptor eventos falsos con firma válida. Después, revisa en tu sistema los eventos aceptados desde la filtración y contrástalos con el historial de entregas: una entrega auténtica siempre aparece ahí con su event_id.

En el sandbox #

Con una llave de sandbox, rotar devuelve otro secreto fijo (whsec_sbx_…), distinto del de creación pero siempre el mismo para ese endpoint. Sirve para practicar el flujo; no hay entregas reales que se rechacen. Ver Probar tus webhooks.