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

# Rotate a webhook secret

> Issues a new secret and shows it once. The URL does not change. `secretKind: "link"` with `linkAcknowledged: true` issues a link secret and answers its `link`; without `secretKind`, the new secret is a header one, even on a link webhook.

**The previous secret, and any previous link, stop working immediately — there is no overlap. Whatever is POSTing to this URL fails until you give it the new secret or link.**



## OpenAPI

````yaml /openapi.json post /api/v1/webhooks/{id}/rotate
openapi: 3.1.0
info:
  title: Ergoly platform API
  version: 1.0.0
  description: >-
    Read and change one organization's own Ergoly environment. Every route is
    scoped to the organization the presented credential resolves to; there is no
    parameter that widens it. The human-readable reference is served from the
    same namespace at `/api/v1/docs`, behind the same credential.


    The namespace's front door is `GET /api/v1`, and it is deliberately absent
    from `paths` below. It answers without a credential — what this API is, how
    an organization admin mints a key, and where the reference lives — so it is
    the way in to this contract rather than a part of it: it has no scope, takes
    no parameters, and there is nothing about it for a generated client to call.
    Every `unauthorized` refusal carries that same URL as `error.docs`.
servers:
  - url: https://control.ergoly.ai
security:
  - orgApiKey: []
tags:
  - name: auth
    description: How to present a key, what it is scoped to, and what a 401 means.
  - name: organization
    description: Which organization your credential resolves to.
  - name: workers
    description: The Slack apps and bot identities in your organization.
  - name: eworks
    description: The runtime instances under a Worker — one core, one per bound Client.
  - name: clients
    description: The customers a Worker can be scoped to, and how to bind one.
  - name: channels
    description: Which Slack rooms a Worker answers in, and who it answers as.
  - name: tasks
    description: >-
      Tasks handed between Workers and people, scheduled jobs, their targets,
      and their run history.
  - name: jobs
    description: How a provisioning run is going — the other half of every 202.
  - name: memory
    description: Read and write the memory tiers an ework mounts.
  - name: connections
    description: The credentials your agents use, and the hosts they are injected on.
  - name: webhooks
    description: Inbound URLs an outside system can POST to, each of which starts a turn.
  - name: activity
    description: Your organization's audit feed.
  - name: usage
    description: Cost, turn outcomes and a per-day pulse over a window you choose.
  - name: errors
    description: The closed set of refusal codes, and what to do about each.
paths:
  /api/v1/webhooks/{id}/rotate:
    post:
      tags:
        - webhooks
      summary: Rotate a webhook secret
      description: >-
        Issues a new secret and shows it once. The URL does not change.
        `secretKind: "link"` with `linkAcknowledged: true` issues a link secret
        and answers its `link`; without `secretKind`, the new secret is a header
        one, even on a link webhook.


        **The previous secret, and any previous link, stop working immediately —
        there is no overlap. Whatever is POSTing to this URL fails until you
        give it the new secret or link.**
      operationId: rotateWebhookSecret
      parameters:
        - name: id
          in: path
          required: true
          description: >-
            The webhook id. A malformed id answers 404, exactly as another
            organization's well-formed id does.
          schema:
            type: string
            description: >-
              The webhook id. A malformed id answers 404, exactly as another
              organization's well-formed id does.
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Any unique string. A retry under the same key is answered from the
            first attempt's record, with `X-Idempotent-Replay: true`. The same
            key with a different body is a conflict.
          schema:
            type: string
            description: Any unique string.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                secretKind:
                  type: string
                  enum:
                    - header
                    - link
                  description: >-
                    `header` (absent) mints a `whsec_…` secret the sender puts
                    in `Authorization: Bearer`. `link` mints a `whlnk_…` secret
                    carried in the URL, for a sender that takes only a web
                    address (Calendly, Cal.com), and answers `link` beside
                    `secret`. A rotate without `link` turns a link webhook into
                    a header one.
                linkAcknowledged:
                  type: boolean
                  description: >-
                    Required, and `true`, with `secretKind: "link"`: you accept
                    that the tool you paste the link into keeps a copy of it,
                    and that a link carrying a secret is less safe than a secret
                    sent in a header. Without it the call is a 400 and nothing
                    is changed.
              required: []
              additionalProperties: false
            example: {}
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/RotatedWebhook'
                required:
                  - data
                additionalProperties: false
              example:
                data:
                  webhook:
                    id: f2e1d0c9-b8a7-4655-9243-1c0b9a8d7e6f
                    organizationId: 0d4c9f2e-4b8a-4a5b-9e21-6f9c2a1b7d30
                    workerId: 8f1c2d3e-9a4b-4c5d-8e6f-1a2b3c4d5e6f
                    clientId: null
                    workClientId: c1a2b3c4-d5e6-4f70-8912-3a4b5c6d7e8f
                    name: Deploy notifications
                    description: Our CI posts here when a release finishes.
                    url: >-
                      https://api.ergoly.ai/api/hooks/f2e1d0c9-b8a7-4655-9243-1c0b9a8d7e6f
                    surface:
                      kind: channel
                      channelId: C0A1B2C3D4E
                      slackUserId: null
                    threading: top_level
                    taskId: 5d6e7f80-91a2-4b3c-8d4e-5f60718293a4
                    runMode: overlap
                    keepMemory: false
                    threadTs: null
                    rootText: null
                    promptTemplate: null
                    enabled: true
                    createdBy: api
                    lastDeliveredAt: '2026-03-04T11:02:31.000Z'
                    revokedAt: null
                    createdAt: '2026-02-28T14:10:00.000Z'
                    updatedAt: '2026-03-04T11:02:31.000Z'
                  url: >-
                    https://api.ergoly.ai/api/hooks/f2e1d0c9-b8a7-4655-9243-1c0b9a8d7e6f
                  secret: >-
                    whsec_9f2c1b7a4e6d08135a29c4b7e0d6f381a2c5b9e04d7f1638a2b4c6d8e0f1a3b5
        '400':
          description: '`invalid_request`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: '`unauthorized`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: '`not_found`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: '`rate_limited`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - orgApiKey:
            - webhooks:write
components:
  schemas:
    RotatedWebhook:
      type: object
      properties:
        webhook:
          $ref: '#/components/schemas/Webhook'
        url:
          type: string
          description: Unchanged — a rotation does not move the endpoint.
        secret:
          type: string
          description: >-
            The new credential, SHOWN ONCE: `whsec_…` for a header, `whlnk_…` in
            link mode. The previous secret, and any previous link, stopped
            working the moment this was issued; there is no overlap.
        link:
          type: string
          description: >-
            Link mode only, and absent otherwise: the URL with the secret in it
            (`?token=whlnk_…`), for a sender that takes a web address and cannot
            set a header. SHOWN ONCE. The link is the credential: treat it like
            a password.
      required:
        - webhook
        - url
        - secret
      additionalProperties: false
    Error:
      type: object
      description: The one error shape. Every refusal in this namespace has it.
      properties:
        error:
          type: object
          description: The refusal.
          properties:
            code:
              type: string
              enum:
                - unauthorized
                - not_found
                - invalid_request
                - conflict
                - quota_exceeded
                - rate_limited
                - upstream_error
                - gone
              description: The machine-stable half. Branch on this, never on `message`.
            message:
              type: string
              description: Prose for a human reading a terminal.
            docs:
              type: string
              description: >-
                Where to learn how to authenticate. Present on `unauthorized`
                and on nothing else — it is the one refusal a caller can reach
                with no credential at all, so it is the one that has to say
                where the way in is.
          required:
            - code
            - message
          additionalProperties: false
      required:
        - error
      additionalProperties: false
    Webhook:
      type: object
      description: An inbound URL that starts a turn.
      properties:
        id:
          type: string
          description: The webhook id. It is also the URL segment deliveries are POSTed to.
        organizationId:
          type: string
          description: Always your own organization.
        workerId:
          type: string
          description: The Worker whose agent takes the delivery's turn.
        clientId:
          type:
            - string
            - 'null'
          description: >-
            The Client whose scope owns this webhook. Null on a core-scoped one,
            which is what a key creates. Where its deliveries run is
            `workClientId`.
        workClientId:
          type:
            - string
            - 'null'
          description: >-
            Null when the work is no one client's: an internal room, a DM, a
            task whose targets name several clients, or a revoked webhook, which
            has let go of its task (`taskId: null`). Otherwise the Client whose
            work each delivery is, read from its task: a webhook on a room
            mapped to a client runs on that client's agent.
        name:
          type: string
          description: >-
            What this endpoint is called. It is what the agent is told the
            delivery came from.
        description:
          type:
            - string
            - 'null'
          description: Free text for whoever maintains it.
        url:
          type: string
          description: Where to POST. Absolute, so you need no base URL of your own.
        surface:
          type: object
          description: Where the worker posts each delivery's result.
          properties:
            kind:
              type: string
              enum:
                - channel
                - dm
              description: A room, or a direct message with one person.
            channelId:
              type:
                - string
                - 'null'
              description: The Slack channel id, on a `channel` webhook.
            slackUserId:
              type:
                - string
                - 'null'
              description: The Slack user id, on a `dm` webhook.
          required:
            - kind
            - channelId
            - slackUserId
          additionalProperties: false
        threading:
          type: string
          enum:
            - top_level
            - new_thread
            - same_thread
          description: >-
            The older way to set `runMode` and `keepMemory`. `top_level` and
            `new_thread` behave alike: events overlap, each run with its own
            conversation. `same_thread` runs events one at a time on the task's
            one shared conversation; it is not one Slack thread.
        taskId:
          type:
            - string
            - 'null'
          description: >-
            The task each delivery starts a run on. Its destination is where
            results go; null once a revoke has let go of it.
        runMode:
          type: string
          enum:
            - overlap
            - one_at_a_time
          description: >-
            `overlap` runs events side by side, each with its own conversation.
            `one_at_a_time` runs them in turn on the task's shared conversation.
        keepMemory:
          type: boolean
          description: >-
            True when every event run shares the task's one conversation, so a
            later event remembers an earlier one. Implies `one_at_a_time`.
        threadTs:
          type:
            - string
            - 'null'
          description: 'Unused: nothing reads or writes it.'
        rootText:
          type:
            - string
            - 'null'
          description: 'Unused: stored, and nothing reads it.'
        promptTemplate:
          type:
            - string
            - 'null'
          description: >-
            What the agent is asked to do, with the payload appended below it —
            or placed at `{{payload}}` when the template names that. Null uses
            the built-in wording.
        enabled:
          type: boolean
          description: >-
            False once it is disabled or revoked. Every delivery to a disabled
            endpoint answers 410.
        createdBy:
          type: string
          enum:
            - operator
            - ework
            - api
          description: Which surface minted it. Audit only; nothing behaves differently.
        lastDeliveredAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When it last accepted a delivery.
        revokedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When it was retired. A revoke is a one-way door.
        createdAt:
          type: string
          format: date-time
          description: When it was created.
        updatedAt:
          type: string
          format: date-time
          description: When it was last changed.
      required:
        - id
        - organizationId
        - workerId
        - clientId
        - workClientId
        - name
        - description
        - url
        - surface
        - threading
        - taskId
        - runMode
        - keepMemory
        - threadTs
        - rootText
        - promptTemplate
        - enabled
        - createdBy
        - lastDeliveredAt
        - revokedAt
        - createdAt
        - updatedAt
      additionalProperties: false
  securitySchemes:
    orgApiKey:
      type: http
      scheme: bearer
      description: >-
        An organization-scoped API key, minted in control under Settings → API
        keys. Every failure to authenticate answers one identical 401: "Provide
        a valid organization-scoped API key or an active session." Each
        operation names the one scope it is filed under,
        `<resource>:<read|write>`, where the resource is the first path segment
        after `/api/v1/`; a key minted today carries all 22 of them. Scopes are
        RECORDED — on the key and on every audit row — and are not yet enforced:
        no request is refused for want of one.

````

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