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

# Construye tu propia ventana emergente de vinculación

> Modo directo — conecta una red cada vez desde tu propio botón, con una ventana emergente que abres y supervisas tú mismo.

export const PlansAvailable = ({plans = [], maxPackRequired}) => {
  let displayPlans = plans;
  if (plans && plans.length === 1) {
    const lowerCasePlan = plans[0].toLowerCase();
    if (lowerCasePlan === "business") {
      displayPlans = ["Launch", "Business", "Enterprise"];
    } else if (lowerCasePlan === "premium") {
      displayPlans = ["Premium", "Launch", "Business", "Enterprise"];
    }
  }
  return <Note>
Available on {displayPlans.length === 1 ? "the " : ""}
{displayPlans.join(", ").replace(/\b\w/g, l => l.toUpperCase())}{" "}
{displayPlans.length > 1 ? "plans" : "plan"}.

{maxPackRequired && <span onClick={() => window.open('https://www.ayrshare.com/docs/additional/maxpack', '_self')} className="flex items-center mt-2 cursor-pointer">
 <span className="px-1.5 py-0.5 rounded text-sm" style={{
    backgroundColor: '#C264B6',
    color: 'white',
    fontSize: '12px'
  }}>
   Max Pack required
 </span>
</span>}
</Note>;
};

<PlansAvailable plans={["business"]} maxPackRequired={true} />

El modo directo (Direct Mode) conecta **una red social cada vez**, desde un botón en tu propio
dashboard. Creas una sesión de vinculación para esa red, abres la URL que devuelve en una ventana
emergente, y tu página se entera de lo que ocurrió. Tu usuario nunca ve una página con la lista de
todas las redes, y nunca abandona tu app durante más tiempo del que tarda el login de la propia red.

## Qué superficie quieres

Tres formas, y las dos primeras son la misma integración. Esta página es la que construyes tú mismo.

| Superficie | Tu usuario ve | Marca blanca | Elígela cuando |
| - | - | - | - |
| **Widget — frames incrustados** ([mount](/docs/multiple-users/connect-widget#mount-a-slot)) | nuestros botones integrados en tu propio layout; sin ventana emergente hasta la de la propia red | **La más fuerte.** Tu página, tus fuentes y colores, y tu usuario nunca la abandona | tienes un dashboard con una fila por red y quieres que la vinculación ocurra ahí mismo. Max Pack. |
| **Widget — tu propio botón** ([popup](/docs/multiple-users/connect-widget#your-own-button)) | tu botón, y después una única ventana emergente para la red | **Fuerte.** La ventana emergente es nuestra, pero es breve y hereda tu apariencia | quieres tu propio botón y tus estilos, y ningún frame en tu layout. Max Pack. |
| **Página de vinculación alojada** ([cómo hacerlo](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | una página alojada por nosotros, con tu logo, tus colores y tu CSS personalizado | **La más débil.** Es nuestra página, y tu usuario abandona la tuya para usarla | quieres un único enlace que repartir, o incorporas usuarios por correo. Nada que construir, sin Max Pack. |

<Note>
  **El modo directo es la ventana emergente de la tercera fila sin nuestro script.** Si tu página
  puede cargar el script, el [widget](/docs/multiple-users/connect-widget) hace por ti todo lo de esta
  página — abre y supervisa la ventana emergente él mismo, y además puede incrustar frames. El modo
  directo es lo que quieres cuando tu página no puede cargar un script de terceros, o la superficie
  es una app nativa en lugar de un navegador.
</Note>

<Frame caption="Tu botón, tu página. La ventana emergente es lo único nuestro que ve tu usuario, y solo durante el tiempo que la red necesita.">
  <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-popup.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=b68021d477703d97a5994ee85d299be6" alt="A customer dashboard with its own Connect buttons and an Ayrshare popup showing the Facebook hand-off screen" width="2880" height="1317" data-path="images/multiple-users/connect-widget-popup.webp" />
</Frame>

## Qué construyes

Cuatro pasos. El primero está en tu servidor, el resto en tu página.

<Steps>
  <Step title="Crea una sesión para una red">
    Desde tu backend, llama a
    [Crear una Link Session](/docs/apis/profiles/create-link-session) con `mode: "connect"`, la
    `network` y el `origin` en el que corre tu página.

    ```javascript Your backend theme={"system"}
    const response = await fetch("https://api.ayrshare.com/api/profiles/link-sessions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.AYRSHARE_API_KEY}`,
        "Profile-Key": profileKey,
      },
      body: JSON.stringify({
        mode: "connect",
        network: "reddit",
        origin: "https://app.example.com",
      }),
    });

    const { url } = await response.json();
    ```

    Recibes de vuelta una `url` que apunta a una página de conexión de una sola red, y ningún
    `token` — el token está dentro de la URL. Trata la URL completa como una contraseña: inicia la
    sesión de tu usuario en su User Profile.

    <Warning>
      Crea la sesión en tu servidor, nunca en el navegador. La llamada necesita tu API Key.
    </Warning>
  </Step>

  <Step title="Ábrela en el manejador de clic, de forma síncrona">
    La ventana emergente tiene que abrirse con `window.open` **en el propio manejador de clic**. Un
    navegador solo permite una ventana emergente mientras todavía está procesando el clic de tu
    usuario, y ese permiso no sobrevive a un `await` — así que obtener primero la URL y abrirla en el
    callback acaba bloqueado por el navegador de forma fiable.

    Obtén la URL cuando renderices el botón, o cuando tu usuario pase el cursor por encima. Para
    cuando llegue el clic ya deberías tenerla.

    ```javascript Your page theme={"system"}
    // `url` was fetched earlier. Nothing async between the click and window.open.
    button.addEventListener("click", () => {
      const popup = window.open(url, "ayrshare-connect", "width=600,height=800");
      if (!popup) {
        showError("Allow popups for this site to connect an account.");
        return;
      }
      listenForOutcome(popup, url);
    });
    ```
  </Step>

  <Step title="Escucha el resultado">
    Como pasaste `origin`, la ventana emergente envía un evento a tu página por cada cosa que
    ocurre en ella: `connect:success`, `connect:error`, `connect:cancelled`, y eventos de progreso
    entre medias. Llega exactamente uno de esos tres por conexión.

    [Eventos de finalización de vinculación](/docs/multiple-users/link-completion-events) tiene la tabla
    completa de eventos y un listener para copiar y pegar — `listenForOutcome` de arriba es ese
    snippet. Hay dos partes de él fáciles de omitir y ambas causan bugs reales:

    * **Comprueba `event.origin`** contra el origen de la URL que abriste. Cualquier página puede
      enviar un mensaje a tu ventana, y el origen es la única parte de un mensaje que no se puede
      falsificar.
    * **Haz polling de `popup.closed`**, con una pequeña ventana de gracia antes de concluir nada.
      Una ventana emergente que tu usuario cerró a mano no envía absolutamente nada, y sin la
      ventana de gracia una conexión con éxito puede reportarse como cancelada.
  </Step>

  <Step title="Gestiona cada final">
    | Final | Qué significa | Qué hacer |
    | - | - | - |
    | `connect:success` | la cuenta está conectada **y guardada** | refresca esa fila. Un [`GET /user`](/docs/apis/user/profile-details) justo después ya la muestra. |
    | `connect:error` | la conexión falló | muestra `message`. `code` está presente cuando el fallo tiene un código catalogado y ausente cuando no. |
    | `connect:cancelled` | tu usuario se echó atrás, o cerró la ventana | deja la fila como estaba. `reason` es `popupClosed`, `scopesDenied` o `userCancelled`. |
    | no llega nada | el enlace estaba caducado, revocado o era desconocido, así que la página nunca supo adónde enviar los eventos | haz polling de [Obtener una Link Session](/docs/apis/profiles/get-link-session), que informa de esos estados con autoridad. |
  </Step>
</Steps>

<Tip>
  Añade `&autoClose=false` a la URL mientras construyes. La ventana emergente entonces se queda
  abierta tras cada resultado en lugar de cerrarse sola, para que puedas leer lo que dice.
</Tip>

## Notas por red

La mayoría de las redes son una ventana emergente y nada más: tu usuario hace clic, autoriza en la
red, y la ventana emergente se cierra. Estas son las excepciones que conviene conocer antes de
construir.

<AccordionGroup>
  <Accordion title="X requiere tus propias API keys">
    X en modo directo usa **tus** credenciales de X Developer App, proporcionadas al crear la
    sesión como las cabeceras `X-Twitter-OAuth1-Api-Key` y `X-Twitter-OAuth1-Api-Secret` en
    [Crear una Link Session](/docs/apis/profiles/create-link-session).

    Una sesión creada para `twitter` o `x` **sin** esas cabeceras se rechaza cuando la ventana
    emergente se abre: a tu usuario se le dice que la conexión no está disponible y no se le muestra
    ningún formulario, y tu página recibe `connect:error` con un `message` y **sin `code`**. Eso es
    deliberado. La credencial que falta es tuya, no de tu usuario, y a los usuarios finales nunca se
    les debe pedir que escriban tus API keys.

    Contrasta con Bluesky, donde la app password es la credencial propia del usuario final — esa sí
    la recoge la página de conexión, en un formulario dentro de la ventana emergente.
  </Accordion>

  <Accordion title="Facebook muestra un botón antes del login de Meta">
    `network: "facebook"` muestra un único botón en la ventana emergente, y el login propio de Meta
    se abre a partir de ese clic — Meta exige que su login lo inicie un clic dentro de la página que
    aloja su SDK. Tu usuario hace clic dos veces en lugar de una; nada más difiere.

    Instagram se comporta igual cuando se vincula **a través de una Facebook Page** — es decir,
    cuando la sesión lleva `instagramLinkMethod: "facebook"`, o cuando el ajuste
    [Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) de tu cuenta selecciona
    ese flujo. Con Instagram Login directo no hay botón extra.
  </Accordion>

  <Accordion title="Bluesky y Telegram muestran contenido en la página, no una redirección">
    Ninguna de las dos envía a tu usuario a un login de la red. La ventana emergente renderiza
    contenido en su lugar: un formulario de handle y app password para Bluesky, y un código que usar
    para Telegram. Los eventos de resultado son los mismos en cualquier caso.

    X no pertenece a este grupo. Con tus keys en la sesión completa el proceso sin pedirle nada a tu
    usuario, y sin ellas se rechaza — mira arriba.
  </Accordion>

  <Accordion title="Telegram termina fuera de banda">
    Telegram muestra un código en lugar de redirigir a ninguna parte, y la conexión se completa
    cuando tu usuario usa ese código — después de que la ventana emergente haya desaparecido. No hay
    ningún evento de navegador que esperar, así que haz polling de
    [Obtener una Link Session](/docs/apis/profiles/get-link-session) y observa `completedNetworks`.
  </Accordion>

  <Accordion title="Los grupos de Facebook no pueden conectarse de esta manera">
    Facebook Groups no es un destino de vinculación, así que `network: "fbg"` devuelve `code: 508`
    al crear la sesión.

    WhatsApp **sí** está disponible en modo directo. Abre el Embedded Signup de Meta en la ventana
    emergente, y los eventos de resultado son los mismos que con cualquier otra red.
  </Accordion>
</AccordionGroup>

<h2 id="native-apps">
  Apps nativas
</h2>

Una app nativa abre la misma `url`, en el **navegador del sistema**, y averigua el resultado haciendo
polling de [Obtener una Link Session](/docs/apis/profiles/get-link-session). Establece `origin` con tu
esquema personalizado (`myapp://connected`) para que la página tenga una vía de vuelta a tu app; un
esquema personalizado no puede recibir eventos, porque no hay ventana de navegador a la que
enviarlos.

* **iOS** — `ASWebAuthenticationSession`, o `SFSafariViewController`.
* **Android** — Chrome Custom Tabs.

<Warning>
  **Nunca abras una URL de vinculación en un webview incrustado** (`WKWebView`, `UIWebView`,
  `WebView` de Android). Las redes sociales se niegan a autenticar en uno: Google rechaza el inicio
  de sesión con `disallowed_useragent`, y Meta lo bloquea directamente. Tu usuario ve la página de
  error de la propia red, no la nuestra, y nada que puedas cambiar en tu lado lo arregla. Los
  componentes de navegador del sistema de arriba existen exactamente por esta razón y mantienen al
  usuario dentro de tu app.
</Warning>

## Qué requiere el modo directo

<ul className="custom-bullets">
  <li>
    El **[Max Pack](/docs/additional/maxpack)**. Crear una sesión en modo connect sin él devuelve
    `code: 504`, diga lo que diga el resto de la solicitud.
  </li>

  <li>
    Un **`origin`** en cada sesión. No hay allowlist ni paso de registro — lo envías en cada
    llamada. Omitirlo devuelve `code: 505`; un valor que no sea un origen `https`, un esquema
    personalizado o `http://localhost` devuelve `code: 506`.
  </li>

  <li>
    Una **`network`** que tu cuenta tenga habilitada. Un nombre no reconocido devuelve `code: 508`;
    uno reconocido que tu cuenta no ha habilitado devuelve `code: 509`, que puedes arreglar en tu
    página [Social Networks](/docs/multiple-users/manage-user-profiles#set-social-networks-access).
  </li>

  <li>
    **No** `allowedSocial`. No puede combinarse con `network` (`code: 507`) — una sesión de una sola
    red ya es su propia allowlist.
  </li>
</ul>

Cada uno de estos está en la referencia de
[Errores de link session](/docs/errors/errors-ayrshare#link-session-errors), con el mensaje que devuelve
la API.

## Próximos pasos

<Card title="Eventos de finalización de vinculación" icon="tower-broadcast" href="/docs/multiple-users/link-completion-events" horizontal>
  Todos los eventos que envía la ventana emergente, y el listener para recibirlos.
</Card>

## Relacionado

<Card title="Crear una Link Session" icon="link" href="/docs/apis/profiles/create-link-session#connect-mode" horizontal>
  Los parámetros `mode`, `origin` y `network`, y las formas de la respuesta.
</Card>

<Card title="Obtener una Link Session" icon="magnifying-glass" href="/docs/apis/profiles/get-link-session" horizontal>
  Haz polling de la finalización cuando no puedas usar una ventana emergente.
</Card>


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