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

# Set Flow endpoint destination

> Stores the customer's OWN webhook — the destination we forward each decrypted Flow exchange to. **This does NOT touch Meta.** Registering `endpoint_uri` on the Flow is a separate act against a separate system (`POST` on this same resource); doing it here would silently re-point the Flow of a tenant who already runs their own endpoint, on a request that looked like a rename. What this answers instead is where Meta IS pointing — `metaEndpointUri`, `drift` and `warnings`.

⛔ `url` must be `https://`. The body forwarded to it is the END USER's form answers, decrypted by us out of Meta's envelope one hop earlier; over `http://` that is PII in the clear, and there is no flag that allows it (400 `FLOW_ENDPOINT_URL_NOT_HTTPS`). The refused URL is not echoed back — it may carry a token in its query string.

⚠️ `url` is REQUIRED and may be `null` EXPLICITLY, never by omission. An absent field and a `null` one mean opposite things — "I sent you a partial body" and "remove the destination" — so an omitted `url` is 400 `FLOW_ENDPOINT_URL_REQUIRED` rather than a wipe of a working configuration. `"url": null` CLEARS the destination and KEEPS the secret; `rotateSecret: true` is the exit for a secret that leaked.

⚠️ `secret` comes back on exactly ONE response in the life of a secret: the request that minted it. It is encrypted at rest and there is no read path back, so `secret: null` means "one already existed and was kept", not "there is none" — `hasSecret` answers that.

A truncated body is not read as an empty one: `{"url": "https://x"` (one missing brace) is 400 `FLOW_BODY_INVALID`. Only `url` and `rotateSecret` are accepted; the number comes from the KEY, so naming it in the body is 400 `FLOW_NUMBER_FROM_KEY`. Permission `flows:manage`.

**Requires a number-scoped key.** A tenant-scoped key must name the number with the `x-whatsapp-number-id` header, or it gets 403 `TENANT_SCOPE_NOT_ALLOWED`.



## OpenAPI

````yaml openapi.json PUT /v1/flows/{id}/endpoint
openapi: 3.1.0
info:
  title: Pilot Status API
  version: 1.0.0
  license:
    name: Pilot Status Terms of Service
    url: https://pilotstatus.com.br/terms
  description: >-
    Public REST API for Pilot Status. Authenticate with the `x-api-key: ps_...`
    header (or `x-api-key-id`). Base URL: https://pilotstatus.com.br
servers:
  - url: https://pilotstatus.com.br
security:
  - apiKey: []
  - apiKeyId: []
paths:
  /v1/flows/{id}/endpoint:
    put:
      tags:
        - Flows
      summary: Set Flow endpoint destination
      description: >-
        Stores the customer's OWN webhook — the destination we forward each
        decrypted Flow exchange to. **This does NOT touch Meta.** Registering
        `endpoint_uri` on the Flow is a separate act against a separate system
        (`POST` on this same resource); doing it here would silently re-point
        the Flow of a tenant who already runs their own endpoint, on a request
        that looked like a rename. What this answers instead is where Meta IS
        pointing — `metaEndpointUri`, `drift` and `warnings`.


        ⛔ `url` must be `https://`. The body forwarded to it is the END USER's
        form answers, decrypted by us out of Meta's envelope one hop earlier;
        over `http://` that is PII in the clear, and there is no flag that
        allows it (400 `FLOW_ENDPOINT_URL_NOT_HTTPS`). The refused URL is not
        echoed back — it may carry a token in its query string.


        ⚠️ `url` is REQUIRED and may be `null` EXPLICITLY, never by omission. An
        absent field and a `null` one mean opposite things — "I sent you a
        partial body" and "remove the destination" — so an omitted `url` is 400
        `FLOW_ENDPOINT_URL_REQUIRED` rather than a wipe of a working
        configuration. `"url": null` CLEARS the destination and KEEPS the
        secret; `rotateSecret: true` is the exit for a secret that leaked.


        ⚠️ `secret` comes back on exactly ONE response in the life of a secret:
        the request that minted it. It is encrypted at rest and there is no read
        path back, so `secret: null` means "one already existed and was kept",
        not "there is none" — `hasSecret` answers that.


        A truncated body is not read as an empty one: `{"url": "https://x"` (one
        missing brace) is 400 `FLOW_BODY_INVALID`. Only `url` and `rotateSecret`
        are accepted; the number comes from the KEY, so naming it in the body is
        400 `FLOW_NUMBER_FROM_KEY`. Permission `flows:manage`.


        **Requires a number-scoped key.** A tenant-scoped key must name the
        number with the `x-whatsapp-number-id` header, or it gets 403
        `TENANT_SCOPE_NOT_ALLOWED`.
      operationId: put_flows_id_endpoint
      parameters:
        - name: id
          in: path
          required: true
          description: >-
            The **local** id of the Flow — the `id` field `GET /v1/flows`
            returns, never `metaFlowId`.
          schema:
            type: string
          example: cmf1a2b3c4d5e6f7g8h9i0j1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  description: >-
                    The customer's own `https://` webhook. `null` CLEARS the
                    destination and keeps the signing secret. Required —
                    omitting the field is 400 `FLOW_ENDPOINT_URL_REQUIRED`,
                    never a wipe.
                  example: https://hooks.acme.com/flows/data-exchange
                rotateSecret:
                  type: boolean
                  description: >-
                    Mints a new HMAC signing secret and returns it once. Must be
                    a real boolean — `"true"` is refused, never coerced (400
                    `FLOW_ENDPOINT_ROTATE_INVALID`).
                  example: false
            example:
              url: https://hooks.acme.com/flows/data-exchange
      responses:
        '200':
          description: >-
            Destination stored. `secret` is present ONLY when this request
            minted one — a rotation, or the first save on this Flow
          content:
            application/json:
              example:
                flowId: cmf1a2b3c4d5e6f7g8h9i0j1
                url: https://hooks.acme.com/flows/data-exchange
                hasSecret: true
                endpointUri: >-
                  https://pilotstatus.com.br/api/flows/endpoint/AbC123.../1122334455
                metaEndpointUri: >-
                  https://pilotstatus.com.br/api/flows/endpoint/AbC123.../1122334455
                drift: false
                numberHasKey: true
                numberKeyUploadedAt: '2026-09-01T18:04:00.000Z'
                warnings: []
                secret: >-
                  9f8a1c0e7b6d5a4c3e2f1b0a9d8c7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d
        '400':
          description: >-
            `FLOW_ENDPOINT_URL_REQUIRED`, `FLOW_ENDPOINT_URL_NOT_HTTPS`,
            `FLOW_ENDPOINT_ROTATE_INVALID`, `FLOW_BODY_INVALID`,
            `FLOW_UNKNOWN_FIELDS` or `FLOW_NUMBER_FROM_KEY`
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: >-
                  url é obrigatório: envie a URL https:// do seu webhook, ou
                  `"url": null` para deixar este Flow sem destino.
                errorEN: >-
                  url is required: send your webhook's https:// URL, or `"url":
                  null` to leave this Flow with no destination.
                code: FLOW_ENDPOINT_URL_REQUIRED
        '401':
          description: Missing or invalid `x-api-key` / `x-api-key-id` header
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Unauthorized
        '403':
          description: >-
            Tenant-scoped key used on a number-scoped endpoint, or the key's
            role lacks the permission
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Tenant-scoped keys cannot call number endpoints
                code: TENANT_SCOPE_NOT_ALLOWED
        '404':
          description: >-
            No Flow with that id inside the key's own WABA. Never 403: a Flow of
            another tenant is indistinguishable from one that does not exist, on
            purpose — a 403 would confirm someone else's id to whoever guessed
            it
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: Flow não encontrado para o número desta chave.
                errorEN: Flow not found for this key's number.
                code: FLOW_NOT_FOUND
        '422':
          description: >-
            The key's number is not a Meta (Cloud API) number or has no WABA
            behind it (`FLOW_REQUIRES_META_NUMBER`), or it is a Meta number
            outside the WABA that owns this Flow and can therefore never send it
            (`FLOW_NUMBER_WABA_MISMATCH`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: >-
                  Flows existem apenas em números Meta (API Oficial). O número
                  desta chave não é Meta ou não tem uma WABA associada — use uma
                  chave de um número Meta.
                errorEN: >-
                  Flows only exist on Meta (Cloud API) numbers. This key's
                  number is not a Meta number, or has no WABA behind it — use a
                  key bound to a Meta number.
                code: FLOW_REQUIRES_META_NUMBER
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Too many requests
        '500':
          description: >-
            `FLOW_ENDPOINT_SECRET_UNAVAILABLE` when the at-rest encryption key
            is not configured — the save is refused rather than storing the
            signing secret in the clear or forwarding unsigned — or the generic
            `INTERNAL_ERROR`. Neither body names an environment variable
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Portuguese half
                  errorEN:
                    type: string
                    description: English half
                  code:
                    type: string
              example:
                error: >-
                  Não foi possível guardar o segredo de assinatura deste
                  endpoint: o serviço não está configurado para armazená-lo com
                  segurança. Fale com o suporte.
                errorEN: >-
                  Could not store this endpoint's signing secret: the service is
                  not configured to keep it safely. Contact support.
                code: FLOW_ENDPOINT_SECRET_UNAVAILABLE
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: Your ps_ API key
    apiKeyId:
      type: apiKey
      in: header
      name: x-api-key-id
      description: API key id (alternative to x-api-key)

````