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

> ## Agent Instructions
> Bridge exposes three integration surfaces: the REST Integrations API for pushing data into Bridge, webhooks for receiving events from Bridge, and the public Custom Connections endpoint that a tenant's landing page posts to.
> The Integrations API host is https://api-connect-us.bridge.new and every request to it must be authenticated with the x-api-key header. Bridge does not use bearer tokens or OAuth.
> The Custom Connections endpoint is different and the Integrations API rules do not apply to it: it has its own host, it takes no x-api-key and no authentication header of any kind, and it is called from a visitor's browser. It is identified by a workspace id and a capture key together with a reCAPTCHA token. Never tell a reader to authenticate it with an API key, and never tell them to keep its capture key out of frontend code - it is designed to live in the page.
> REST error catalog keys use BRIDGE_<DOMAIN>_<NNNN>, for example BRIDGE_CONVERSATION_0001. Some reviewed legacy handlers return a recognized code plus a symbolic suffix separated by |; preserve the complete received value and use the compatible catalog lookup on /error-codes. Custom Connections uses separate BRIDGE.CAMPAIGN_LEADS.<NAME> identifiers on its own error reference. Never invent an error code or confuse the two integration surfaces.
> This documentation covers the Integrations API, webhooks and Custom Connections only. It does not describe the Bridge web application or its internal APIs.
> Before generating an API request, read the relevant OpenAPI specification or the individual endpoint Markdown page. llms-full.txt collects guides and endpoint descriptions; it does not embed every request and response schema. The REST and Custom Connections contracts are separate.
> Preserve the published host, method, path, operationId, authentication, headers, parameter and field names, types, formats, required and nullable fields, enums, defaults, limits, and response codes. Do not invent identifiers, fields, retry rules, or delivery guarantees. When published sources conflict, identify the conflicting passages and ask Bridge support to confirm the current behavior before relying on it.
> Custom Connections is the current name of the landing-page feature previously documented as Lead Capture. The existing /lead-capture documentation URLs, captureKey field, campaign-leads endpoint, and BRIDGE.CAMPAIGN_LEADS error identifiers are unchanged. This is a naming change, not a change to authentication or the API contract.
> For a complete workflow, start with the task guides: /guides/first-conversation, /guides/send-attachments, /guides/send-template, /guides/agent-outbound, /guides/pagination, /guides/identifiers and /guides/responses. Read the endpoint contract as well as the recipe. Outbound requires an existing Contact, but does not require creating a separate Conversation first.
> forceSend=true permits reuse of an existing active conversation; it does not create a fresh one by ignoring it and it is not idempotency. Recovery of a released recoverable conversation can occur even with false. The LeadScout and Agents outbound routes have different line-selection and controller checks; use /guides/agent-outbound.
> For webhook HMAC, follow /webhooks/verifying-signatures exactly: the Python JSON serialization with sorted keys is signed; X-Bridge-Timestamp is not included in that signature. Do not claim a signed timestamp, raw-body signing, exactly-once delivery or a global event order. Use /webhooks/receive-events for durable acceptance and duplicates.
> A 201 message response, 202 handoff or 202 Custom Connections response is not a provider delivery receipt. Do not invent a polling endpoint from messageId, trackingId or requestId. REST validation parsing must tolerate the compatibility difference explained in /error-codes.
> The documentation is available in English (existing root URLs), Spanish (/es/) and Brazilian Portuguese (/pt-br/). Prefer the reader's language when a localized page exists, but preserve the API's exact field names, enums, routes, authentication and examples. Translations describe the same API; they do not introduce regional API hosts or behaviors.
> For coding-assistant setup, use /connect-your-agent, /es/connect-your-agent or /pt-br/connect-your-agent. The Bridge documentation MCP server reads published documentation and has a separate optional feedback tool. Connecting it does not connect the coding assistant to a customer workspace or authorize live business requests.
> For direct HTTP access without MCP, start at https://docs.bridge.new/llms.txt. It lists every published page in English, Spanish and Brazilian Portuguese. Choose /llms-en.json, /llms-es.json or /llms-pt-br.json for a language-specific index, and /llms-full-en.json, /llms-full-es.json or /llms-full-pt-br.json for collected content. /llms-full.txt contains the combined collection. Fetch the original OpenAPI specification for exact schemas.
> For HTTP page reads, follow the .md URLs from the index or request Accept: text/markdown at the canonical page URL. The MCP virtual filesystem uses .mdx page paths instead; .mdx is not the public HTTP export extension. With Search MCP, set language to en, es or pt-BR explicitly; its default is en.
> For integration capabilities and source-selection rules, read https://docs.bridge.new/skill.md. Documentation access does not authorize business API actions; use the actual user request, workspace, credentials and target identifiers for any live integration.

# Guia de integração

> Conecte um formulário de landing page ao Bridge: exemplo, CAPTCHA, consentimento, respostas e tratamento de erros.

## O exemplo

O console administrativo do Bridge fornece um exemplo de integração gerado com a configuração do workspace. A versão ampliada abaixo acrescenta validação e retorno visual do envio. Antes de executá-la, copie os valores de configuração do exemplo gerado para os campos de substituição.

Adapte o formulário e o texto de consentimento à sua página. **Acme Motors é uma empresa fictícia usada no exemplo abaixo.** Substitua esse nome pela empresa que entrará em contato com o visitante e descreva as mensagens de WhatsApp que ele está aceitando receber. Veja os [requisitos de consentimento](https://docs.bridge.new/pt-br/lead-capture/consent.md). O script demonstra validação, retorno visual do envio e tratamento de erros. Mantenha inalterados os campos da requisição e a ação reCAPTCHA da configuração gerada.

```html theme={null}
<!--
  Campaign leads integration example.
  This is a starting point, not a drop-in script: replace the form fields, labels and
  consent copy below with your own. Keep the existing field ids, or update the matching selectors. The API fields and
  reCAPTCHA action remain the values from your generated configuration.
  What this does: collects a phone number (and optionally a name), mints a reCAPTCHA v3
  token, and POSTs the lead to the campaign-leads endpoint. A 202 response means the lead
  was accepted and queued -- a separate service sends the first WhatsApp message later, so
  do not tell the visitor "message sent"; something like "we'll message you shortly" is
  accurate.
-->
<script>
  window.__CAMPAIGN_LEADS__ = {"workspaceId":"YOUR_WORKSPACE_ID","captureKey":"YOUR_CAPTURE_KEY","endpoint":"https://integrations-us.app.bridge.new/api/v1/leadscout/campaign-leads","brandId":null,"recaptchaSiteKey":"YOUR_RECAPTCHA_SITE_KEY","recaptchaAction":"submit_campaign_lead","trackedParams":[]};
</script>
<!-- ============================================================
     EXAMPLE FORM -- replace with your own markup and consent copy.
     Keep the element ids (or update the selectors in the script below to match yours).
     ============================================================ -->
<form id="lead-form" novalidate>
  <label>
    <span>Name (optional)</span>
    <input id="lead-name" type="text" autocomplete="name" />
  </label>
  <label>
    <span>WhatsApp number</span>
    <input id="lead-phone" type="tel" autocomplete="tel" required />
  </label>
  <!-- The wording of this consent statement is yours to write: it is what the visitor is
       agreeing to, and only you know what you will message them and why. -->
  <label>
    <input id="lead-consent" type="checkbox" required />
    <span>I agree to receive WhatsApp messages from Acme Motors about the quote I just requested.</span>
  </label>
  <button type="submit">Submit</button>
  <p id="lead-result" role="status" aria-live="polite"></p>
</form>
<script>
  (function (config) {
    "use strict";
    var form = document.getElementById("lead-form");
    var consentBox = document.getElementById("lead-consent");
    var submitButton = form.querySelector("button[type=submit]");
    var result = document.getElementById("lead-result");
    var consentCapturedAt = null;
    var inFlight = false;
    var accepted = false;
    var submissionStarted = false;
    var responseReceived = false;
    consentBox.addEventListener("change", function () {
      consentCapturedAt = consentBox.checked ? new Date().toISOString() : null;
    });
    function showResult(message) {
      result.textContent = message;
    }
    function loadRecaptcha() {
      return new Promise(function (resolve, reject) {
        if (window.grecaptcha) {
          resolve();
          return;
        }
        var tag = document.createElement("script");
        tag.src = "https://www.google.com/recaptcha/api.js?render=" + config.recaptchaSiteKey;
        tag.onload = function () {
          resolve();
        };
        tag.onerror = function () {
          reject(new Error("Could not load reCAPTCHA. Check the site key and the network."));
        };
        document.head.appendChild(tag);
      });
    }
    // Mint the token on submit, not on page load: a reCAPTCHA v3 token is only valid for
    // ~2 minutes, so minting it earlier means it can already be expired by the time the
    // visitor actually submits the form.
    function mintToken() {
      return loadRecaptcha().then(function () {
        return new Promise(function (resolve, reject) {
          window.grecaptcha.ready(function () {
            window.grecaptcha
              .execute(config.recaptchaSiteKey, { action: config.recaptchaAction })
              .then(resolve, reject);
          });
        });
      });
    }
    // Allowlist, not a denylist: only utm_* params and the names you seeded in
    // trackedParams are collected. The rest of the query string can carry ad-network
    // click ids or session tokens that were never meant to be stored -- over-collection
    // here is irreversible, so when in doubt leave a param out.
    function readCampaignParams() {
      var params = new URLSearchParams(window.location.search);
      var collected = {};
      params.forEach(function (value, key) {
        if (key.indexOf("utm_") === 0 || config.trackedParams.indexOf(key) !== -1) {
          collected[key] = value;
        }
      });
      return collected;
    }
    function buildPayload(token) {
      return {
        workspaceId: config.workspaceId,
        captureKey: config.captureKey,
        cellphone: document.getElementById("lead-phone").value.trim(),
        name: document.getElementById("lead-name").value.trim() || null,
        brandId: config.brandId,
        // origin + pathname only, never the full URL: the query string can carry
        // click ids or session tokens, and UTMs already travel separately in "utm" above.
        landingUri: window.location.origin + window.location.pathname,
        utm: readCampaignParams(),
        consent: { status: "GRANTED", capturedAt: consentCapturedAt },
        recaptchaToken: token,
      };
    }
    form.addEventListener("submit", function (event) {
      event.preventDefault();
      if (inFlight || accepted || !form.reportValidity()) return;
      if (!consentBox.checked || !consentCapturedAt) {
        // A browser can restore a previously checked control without firing change.
        // Ask for a new explicit action instead of inventing its consent timestamp.
        consentBox.checked = false;
        showResult("Please select the WhatsApp consent checkbox before submitting.");
        consentBox.focus();
        return;
      }
      inFlight = true;
      submissionStarted = false;
      responseReceived = false;
      submitButton.disabled = true;
      showResult("Submitting your request...");
      // A token verifies once. Every manual attempt, including a retry after a
      // rejected request, obtains a new token. This example never retries a POST.
      mintToken()
        .then(function (token) {
          var payload = buildPayload(token);
          // The URL must identify the workspace for the browser's CORS preflight.
          var url = config.endpoint + "?workspaceId=" + encodeURIComponent(config.workspaceId);
          submissionStarted = true;
          return fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload),
          });
        })
        .then(function (response) {
          responseReceived = true;
          return response.json().catch(function () { return null; }).then(function (body) {
            if (response.status === 202) {
              accepted = true;
              var reference = body && typeof body.requestId === "string"
                ? " Reference: " + body.requestId
                : "";
              showResult("Request accepted. The business will process your request." + reference);
              return;
            }
            var code = body && body.type ? body.type : "HTTP " + response.status;
            if (response.status === 422 && code === "BRIDGE.CAMPAIGN_LEADS.CAPTCHA_PROVIDER_UNAVAILABLE") {
              throw new Error("Verification is temporarily unavailable. You can try again; a new CAPTCHA token will be created.");
            }
            if (response.status === 403 && code === "BRIDGE.CAMPAIGN_LEADS.CAPTCHA_REJECTED") {
              throw new Error("The verification was rejected. You can try again with a new CAPTCHA token.");
            }
            var detail = body && (body.detail || body.title);
            throw new Error(code + (detail ? ": " + detail : ". Check the form configuration."));
          });
        })
        .catch(function (error) {
          if (submissionStarted && !responseReceived) {
            showResult("We could not confirm whether your request was accepted. Check with the business before submitting it again.");
            return;
          }
          showResult(error.message || "The request could not be completed.");
        })
        .finally(function () {
          inFlight = false;
          submitButton.disabled = accepted;
        });
    });
  })(window.__CAMPAIGN_LEADS__);
</script>
```

## O que o exemplo faz

* Verifica os campos obrigatórios do formulário antes de obter um token de CAPTCHA.
* Registra `consent.capturedAt` quando o visitante marca a caixa de seleção. Se o navegador restaurar um estado antigo com a caixa já marcada, solicita uma nova ação de consentimento.
* Permite um envio por vez e mantém o botão desabilitado após a aceitação, evitando outro envio nessa instância do formulário.
* Apresenta uma resposta `202` como aceita e enfileirada e exibe o `requestId` retornado como referência para suporte. Nunca informa que uma mensagem de WhatsApp foi entregue.
* Mostra falhas recuperáveis de CAPTCHA e gera um token novo a cada tentativa manual. Não repete um POST automaticamente.
* Trata uma falha de rede após o envio como um resultado incerto; não afirma que o lead foi rejeitado nem que é seguro reenviá-lo.

Esse retorno visual é implementado no cliente, não representa uma garantia de deduplicação no backend. Adapte as mensagens visíveis à sua página e use a [referência de erros](https://docs.bridge.new/pt-br/lead-capture/errors.md) para o diagnóstico.

## A chave de captura não é uma chave de API

> [!NOTE]
>
> O guia de [autenticação](https://docs.bridge.new/pt-br/credentials.md) orienta a nunca colocar uma chave no frontend. Essa regra se aplica à chave da API de integrações e continua válida. A `captureKey` é um valor de outro tipo.

Ao contrário da `x-api-key` usada pela API de integrações, a `captureKey` foi projetada para estar no HTML da página, onde qualquer pessoa pode lê-la. Ela não concede acesso de leitura a nenhum dado: apenas identifica a qual workspace pertence um envio. O abuso é limitado por duas outras verificações: o token de reCAPTCHA e a aceitação de envios somente dos domínios cadastrados para o workspace.

Nunca use a chave da API de integrações aqui e nunca a coloque em uma página.

## Inclua o ID do workspace na URL

O ID do workspace deve estar no corpo da requisição **e** na URL, como `?workspaceId=...`. Ele precisa estar nos dois lugares porque o navegador envia uma requisição preliminar de CORS antes da requisição principal. Essa requisição preliminar não contém corpo. Sem o ID na URL, não conseguimos identificar o workspace nem retornar o cabeçalho que autoriza seu domínio, e o navegador bloqueia a requisição antes de enviá-la.

É importante configurar isso corretamente desde o início: essa falha aparece como um erro de CORS no console do navegador, e a requisição principal não chega aos nossos logs, pois não saiu da página.

## Gere o token de CAPTCHA no envio do formulário

Um token reCAPTCHA v3 vale por cerca de dois minutos. Se ele for gerado no carregamento da página, muitas vezes já terá expirado quando a pessoa terminar de preencher o formulário. A rejeição retorna `403`, o que pode parecer um problema de permissão, embora a causa seja a expiração.

## Gere um novo token para cada tentativa

Cada token pode ser verificado uma única vez. Se um envio falhar e você tentar novamente com o mesmo token, a repetição será indistinguível de um ataque e retornará o mesmo erro recebido por um bot. Sempre chame `grecaptcha.execute` novamente antes de tentar outro envio.

## O que enviar

A [referência do endpoint](https://docs.bridge.new/pt-br/api-reference/campaign-leads/registrar-um-lead-de-campanha.md) contém o contrato completo. Estes são os campos cujo significado não fica evidente apenas pelo esquema:

**brandId** `string`

A marca comercializada pela landing page, não o ID de uma linha, canal ou telefone. Obrigatório somente se o workspace tiver marcas habilitadas. **Nunca é necessário informar um ID de linha.**

**landingUri** `string`

A origem e o caminho da página, sem a query string. Os parâmetros UTM são enviados separadamente em `utm`, e a query string completa pode conter IDs de cliques ou tokens de sessão que não devem ser armazenados. Envie `https://example.com/offer`, não `https://example.com/offer?gclid=...`.

**consent** `object`

Sua declaração de que o visitante consentiu e de quando isso aconteceu. Veja os [requisitos de consentimento](https://docs.bridge.new/pt-br/lead-capture/consent.md): o texto de aceite no formulário não é opcional.

**utm** `object`

Coletado da query string com base em uma lista de permissões: parâmetros `utm_*` e quaisquer nomes adicionais configurados no console. Usamos uma lista de permissões, em vez de uma lista de exclusões, porque a coleta excessiva de dados não pode ser desfeita.

## Após o envio

Uma resposta `202` significa que o lead foi aceito e colocado na fila. Isso **não** significa que uma mensagem foi enviada: o envio acontece depois, em um serviço separado. Informe ao visitante que entrará em contato em breve.

Qualquer outra resposta é um erro. Consulte a [referência de erros](https://docs.bridge.new/pt-br/lead-capture/errors.md) para entender cada caso e saber se você pode corrigi-lo.


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