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

# Rota el secreto de firma

> Genera un nuevo secreto de firma de webhook, lo devuelve exactamente una vez, lo persiste como el secreto actual y degrada el secreto anterior con un solapamiento de 24 horas durante el cual ambos siguen siendo válidos para verificación. La entrega firma con el secreto actual. Una suscripción sin secreto de firma en reposo (un sink `pull`) devuelve `422 no_secret_to_rotate`.



## OpenAPI

````yaml es/openapi/v3-current/streaming-hub.yaml post /v1/subscriptions/{id}/secret/rotate
openapi: 3.1.0
info:
  title: Lerian Streaming Hub API
  version: v1.0.0
  contact:
    email: contact@lerian.studio
    name: Lerian Studio
    url: https://lerian.studio
  license:
    name: Lerian Studio General License
  description: >-
    La API de control-plane de Streaming Hub. Streaming Hub es el borde
    gestionado de entrega de eventos de Lerian: consume CloudEvents del backbone
    de streaming interno de la plataforma y los distribuye a los destinos
    externos propios de cada tenant — webhooks, Amazon SQS, RabbitMQ, Amazon
    EventBridge o una bandeja de entrada de tipo pull. Esta API permite a un
    tenant explorar el catálogo de eventos alimentado por el manifest, crear y
    gestionar suscripciones de entrega, verificar y rotar sus credenciales, leer
    la salud de entrega y hacer pull de los eventos a los que tiene derecho.


    Los errores usan un envelope plano `{"error":"<token>"}` (un token de baja
    cardinalidad y legible por máquina — nunca RFC 9457 problem+json). Las
    operaciones de mutación requieren un header `X-Idempotency` para semántica
    at-most-once; una petición reproducida (replay) devuelve la respuesta
    original byte a byte con `X-Idempotency-Replayed: true`. El catálogo y la
    superficie de eventos pull están acotados por tenant a través del JWT
    bearer; los endpoints operacionales de sonda (`/healthz`, `/readyz`,
    `/version`, `/runtime`, `/metrics`) no requieren autenticación. Streaming
    Hub es de código cerrado bajo la Lerian Studio General License.
servers:
  - url: https://streaming-hub.sandbox.lerian.net
security:
  - BearerAuth: []
tags:
  - name: Catalog
    description: >-
      Explora el catálogo de tipos de evento disponibles para suscripción,
      alimentado por el manifest.
  - name: Subscriptions
    description: >-
      Crea, lee, actualiza y elimina suscripciones de entrega, y gestiona el
      ciclo de vida de verificación del destino (ping, verify, credential,
      delegated grant, rotación de secreto, health).
  - name: Event Delivery
    description: >-
      Haz pull de los eventos a los que tienes derecho para una suscripción de
      tipo pull (lectura cursor-as-acknowledgment).
  - name: Admin
    description: >-
      Análisis forense de operador entre tenants. Requiere un scope de
      autorización de operador.
  - name: Operational
    description: >-
      Sondas de liveness, readiness, build, runtime y métricas sin
      autenticación.
paths:
  /v1/subscriptions/{id}/secret/rotate:
    post:
      tags:
        - Subscriptions
      summary: Rota el secreto de firma
      description: >-
        Genera un nuevo secreto de firma de webhook, lo devuelve exactamente una
        vez, lo persiste como el secreto actual y degrada el secreto anterior
        con un solapamiento de 24 horas durante el cual ambos siguen siendo
        válidos para verificación. La entrega firma con el secreto actual. Una
        suscripción sin secreto de firma en reposo (un sink `pull`) devuelve
        `422 no_secret_to_rotate`.
      operationId: rotateSubscriptionSecret
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: >-
            Se generó un nuevo secreto de firma. `signingSecret` se muestra
            exactamente una vez — guárdalo al recibirlo.
          headers:
            X-Idempotency-Replayed:
              $ref: '#/components/headers/IdempotencyReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RotateSecretResponse'
        '400':
          $ref: '#/components/responses/BadRequestOrMissingIdempotency'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '422':
          description: >-
            La suscripción no tiene secreto de firma para rotar — `error` es
            `no_secret_to_rotate`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - BearerAuth: []
components:
  parameters:
    SubscriptionId:
      name: id
      in: path
      required: true
      description: The unique identifier of the subscription (UUIDv7).
      schema:
        type: string
        format: uuid
    IdempotencyKey:
      name: X-Idempotency
      in: header
      required: true
      description: >-
        A client-chosen unique key that makes this mutation at-most-once. A
        mutation sent without it is rejected before any write with `400
        missing_idempotency_key`. Reusing the same key with an identical request
        replays the original response byte-for-byte (with
        `X-Idempotency-Replayed: true`); reusing it with a different request
        body returns `409 idempotency_conflict`. To re-drive a corrected
        request, mint a new key.
      schema:
        type: string
  headers:
    IdempotencyReplayed:
      description: >-
        Presente y con valor `true` cuando esta respuesta es un replay de una
        petición previamente confirmada que lleva la misma clave `X-Idempotency`
        (el handler no volvió a ejecutarse).
      schema:
        type: string
        enum:
          - 'true'
  schemas:
    RotateSecretResponse:
      type: object
      additionalProperties: false
      properties:
        signingSecret:
          type: string
          description: >-
            The new one-time signing secret (write-only). Shown exactly once
            here.
          examples:
            - whsec_1a2b3c4d5e6f7081a2b3c4d5e6f70819
        overlapUntil:
          type: string
          format: date-time
          description: >-
            The moment the previous secret stops being valid for verification
            (UTC, RFC 3339) — a 24-hour dual-sign overlap.
          examples:
            - '2026-06-06T12:00:00Z'
      required:
        - signingSecret
        - overlapUntil
    Error:
      type: object
      additionalProperties: false
      description: >-
        The flat error envelope used across the `/v1` and `/admin` surfaces. It
        carries a single low-cardinality, machine-readable token and never leaks
        secret material or internal detail. (A `403` from the authorization
        decision point is the one exception — its body is plain text.)
      properties:
        error:
          type: string
          description: The machine-readable error token.
          examples:
            - not_found
      required:
        - error
  responses:
    BadRequestOrMissingIdempotency:
      description: >-
        El cuerpo de la petición está malformado (`bad_request`) o falta el
        header obligatorio `X-Idempotency` (`missing_idempotency_key`, rechazado
        antes de cualquier escritura).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: >-
        La autenticación falló, o no hay contexto de tenant confiable. `error`
        es `unauthorized` (cuerpo uniforme — no se revela ninguna razón).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: >-
        El punto de decisión de autorización denegó la petición. El cuerpo es
        texto plano (no el envelope de error JSON).
      content:
        text/plain:
          schema:
            type: string
    NotFound:
      description: >-
        El recurso está ausente, soft-deleted o pertenece a otro tenant — un
        `error` uniforme de `not_found` (sin oráculo de existencia).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    IdempotencyConflict:
      description: >-
        Una duplicada en curso (in-flight), o la misma clave `X-Idempotency` se
        reutilizó con un fingerprint de petición diferente — `error` es
        `idempotency_conflict`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalError:
      description: >-
        Un fallo de infraestructura. `error` es `internal_error` (sanitizado; el
        detalle se registra, nunca se devuelve).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        Un JWT bearer emitido por plugin-auth (lib-auth). La identidad del
        tenant se resuelve a partir de los claims validados del token; la
        superficie `/v1` nunca lee un tenant del cuerpo, del path ni de la
        query. Los llamadores de máquina obtienen un token vía el flujo
        client-credentials de plugin-auth. La superficie `/admin` autoriza
        contra un scope de operador y no lleva contexto de tenant.

````