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

# Definir destino do endpoint do Flow

> Guarda o webhook PRÓPRIO do cliente — o destino para onde reencaminhamos cada troca decifrada do Flow. **Isto NÃO mexe na Meta.** Registar o `endpoint_uri` no Flow é um ato separado, num sistema separado (`POST` neste mesmo recurso); fazê-lo aqui re-apontaria em silêncio o Flow de um tenant que já corre o endpoint dele, num pedido que parecia uma renomeação. O que isto devolve em vez disso é para onde a Meta ESTÁ a apontar — `metaEndpointUri`, `drift` e `warnings`.

⛔ `url` tem de ser `https://`. O corpo reencaminhado para lá são as respostas do formulário do UTILIZADOR FINAL, decifradas por nós do envelope da Meta um salto antes; em `http://` isso é PII em claro, e não há flag que o permita (400 `FLOW_ENDPOINT_URL_NOT_HTTPS`). A URL recusada não é ecoada de volta — pode levar um token na query string.

⚠️ `url` é OBRIGATÓRIO e pode ser `null` EXPLICITAMENTE, nunca por omissão. Um campo ausente e um `null` significam coisas opostas — "mandei-te um corpo parcial" e "remove o destino" — por isso um `url` omitido dá 400 `FLOW_ENDPOINT_URL_REQUIRED` em vez de apagar uma configuração que funciona. `"url": null` LIMPA o destino e MANTÉM o segredo; `rotateSecret: true` é a saída para um segredo que vazou.

⚠️ `secret` volta em exatamente UMA resposta na vida de um segredo: a do pedido que o cunhou. É cifrado em repouso e não há caminho de leitura de volta, portanto `secret: null` significa "já existia um e foi mantido", não "não há nenhum" — quem responde a isso é `hasSecret`.

Corpo truncado não é lido como corpo vazio: `{"url": "https://x"` (uma chaveta a faltar) dá 400 `FLOW_BODY_INVALID`. Só `url` e `rotateSecret` são aceites; o número vem da CHAVE, por isso nomeá-lo no corpo dá 400 `FLOW_NUMBER_FROM_KEY`. Permissão `flows:manage`.

**Exige chave com escopo de número.** Uma chave de tenant precisa nomear o número no header `x-whatsapp-number-id`, senão recebe 403 `TENANT_SCOPE_NOT_ALLOWED`.



## OpenAPI

````yaml openapi.pt.json PUT /v1/flows/{id}/endpoint
openapi: 3.1.0
info:
  title: API Pilot Status
  version: 1.0.0
  license:
    name: Pilot Status Terms of Service
    url: https://pilotstatus.com.br/terms
  description: >-
    API REST pública do Pilot Status. Autentique com o header `x-api-key:
    ps_...` (ou `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: Definir destino do endpoint do Flow
      description: >-
        Guarda o webhook PRÓPRIO do cliente — o destino para onde reencaminhamos
        cada troca decifrada do Flow. **Isto NÃO mexe na Meta.** Registar o
        `endpoint_uri` no Flow é um ato separado, num sistema separado (`POST`
        neste mesmo recurso); fazê-lo aqui re-apontaria em silêncio o Flow de um
        tenant que já corre o endpoint dele, num pedido que parecia uma
        renomeação. O que isto devolve em vez disso é para onde a Meta ESTÁ a
        apontar — `metaEndpointUri`, `drift` e `warnings`.


        ⛔ `url` tem de ser `https://`. O corpo reencaminhado para lá são as
        respostas do formulário do UTILIZADOR FINAL, decifradas por nós do
        envelope da Meta um salto antes; em `http://` isso é PII em claro, e não
        há flag que o permita (400 `FLOW_ENDPOINT_URL_NOT_HTTPS`). A URL
        recusada não é ecoada de volta — pode levar um token na query string.


        ⚠️ `url` é OBRIGATÓRIO e pode ser `null` EXPLICITAMENTE, nunca por
        omissão. Um campo ausente e um `null` significam coisas opostas —
        "mandei-te um corpo parcial" e "remove o destino" — por isso um `url`
        omitido dá 400 `FLOW_ENDPOINT_URL_REQUIRED` em vez de apagar uma
        configuração que funciona. `"url": null` LIMPA o destino e MANTÉM o
        segredo; `rotateSecret: true` é a saída para um segredo que vazou.


        ⚠️ `secret` volta em exatamente UMA resposta na vida de um segredo: a do
        pedido que o cunhou. É cifrado em repouso e não há caminho de leitura de
        volta, portanto `secret: null` significa "já existia um e foi mantido",
        não "não há nenhum" — quem responde a isso é `hasSecret`.


        Corpo truncado não é lido como corpo vazio: `{"url": "https://x"` (uma
        chaveta a faltar) dá 400 `FLOW_BODY_INVALID`. Só `url` e `rotateSecret`
        são aceites; o número vem da CHAVE, por isso nomeá-lo no corpo dá 400
        `FLOW_NUMBER_FROM_KEY`. Permissão `flows:manage`.


        **Exige chave com escopo de número.** Uma chave de tenant precisa nomear
        o número no header `x-whatsapp-number-id`, senão recebe 403
        `TENANT_SCOPE_NOT_ALLOWED`.
      operationId: put_flows_id_endpoint
      parameters:
        - name: id
          in: path
          required: true
          description: >-
            O id **local** do Flow — o campo `id` que `GET /v1/flows` devolve,
            nunca o `metaFlowId`.
          schema:
            type: string
          example: cmf1a2b3c4d5e6f7g8h9i0j1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  description: >-
                    O webhook `https://` próprio do cliente. `null` LIMPA o
                    destino e mantém o segredo de assinatura. Obrigatório —
                    omitir o campo dá 400 `FLOW_ENDPOINT_URL_REQUIRED`, nunca
                    uma limpeza.
                  example: https://hooks.acme.com/flows/data-exchange
                rotateSecret:
                  type: boolean
                  description: >-
                    Cunha um novo segredo HMAC de assinatura e devolve-o uma
                    única vez. Tem de ser um booleano de verdade — `"true"` é
                    recusado, nunca convertido (400
                    `FLOW_ENDPOINT_ROTATE_INVALID`).
                  example: false
            example:
              url: https://hooks.acme.com/flows/data-exchange
      responses:
        '200':
          description: >-
            Destino guardado. `secret` só vem presente quando este pedido cunhou
            um — uma rotação, ou a primeira gravação neste 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` ou `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: Header `x-api-key` / `x-api-key-id` ausente ou inválido
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Unauthorized
        '403':
          description: >-
            Chave com escopo de tenant usada em endpoint com escopo de número,
            ou o papel da chave não tem a permissão
          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: >-
            Nenhum Flow com esse id dentro da WABA da própria chave. Nunca 403:
            um Flow de outro tenant é indistinguível de um que não existe, de
            propósito — um 403 confirmaria o id alheio a quem o adivinhou
          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: >-
            O número da chave não é Meta (API Oficial) ou não tem WABA associada
            (`FLOW_REQUIRES_META_NUMBER`), ou é um número Meta fora da WABA dona
            deste Flow e por isso nunca poderá enviá-lo
            (`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: Limite de taxa excedido
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Too many requests
        '500':
          description: >-
            `FLOW_ENDPOINT_SECRET_UNAVAILABLE` quando a chave de cifra em
            repouso não está configurada — a gravação é recusada em vez de
            guardar o segredo de assinatura em claro ou reencaminhar sem
            assinatura — ou o genérico `INTERNAL_ERROR`. Nenhum dos corpos
            nomeia variável de ambiente
          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: Sua chave de API ps_
    apiKeyId:
      type: apiKey
      in: header
      name: x-api-key-id
      description: Id da chave de API (alternativa ao x-api-key)

````