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

# Resumen de la vinculación social

> Cómo conectan tus usuarios sus cuentas sociales — las tres superficies, la sesión de vinculación que hay detrás de todas ellas y las opciones que comparten.

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={false} />

Tus usuarios conectan sus propias cuentas sociales por sí mismos — se autentican directamente en
cada red, y tú nunca ves ni almacenas sus credenciales. Todo lo que hay en esta página trata de
llevarlos hasta ese momento y de averiguar cómo fue.

Hay algo común a todas las vías: una **sesión de vinculación**, creada con
[Crear una Link Session](/docs/apis/profiles/create-link-session) a partir de tu API Key y una `Profile-Key`.
No se firma nada en tu lado y no hay ninguna clave privada en el flujo.

## Tres formas de conectar

Tres formas, y las dos primeras son la misma integración. Elige por flujo — no son
excluyentes, y muchas integraciones usan la página alojada para la incorporación y el widget dentro
de la app después.

| 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>
  Las dos filas del widget son **una sola integración**, no dos. Un único `init` te da ambas: monta
  frames donde quieras nuestros botones y llama a `popup()` desde tu propio botón en el resto de
  sitios. Comparten una misma sesión y reportan en los mismos handlers.

  El [modo directo](/docs/multiple-users/connect-direct-mode) (Direct Mode) es la misma ventana emergente
  **sin** nuestro script — para una página con una Content-Security-Policy estricta, una página
  renderizada en servidor o una app nativa. Ahí abres y supervisas la ventana emergente tú mismo.

  **¿No lo tienes claro?** Empieza con la página de vinculación alojada. No necesita Max Pack ni
  parámetros adicionales, y es la vía más rápida hacia algo que funcione — pasar después al widget
  no cambia cómo se crean las sesiones.
</Note>

## Crear un enlace

Llama a [Crear una Link Session](/docs/apis/profiles/create-link-session) con la `Profile-Key` del
usuario en la cabecera. Para la página alojada, esa es toda la solicitud:

```bash cURL theme={"system"}
curl -X POST https://api.ayrshare.com/api/profiles/link-sessions \
  -H "Authorization: Bearer $AYRSHARE_API_KEY" \
  -H "Profile-Key: $PROFILE_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Recibes de vuelta una `url` que lleva un token opaco y de corta duración:

```javascript Linking URL theme={"system"}
https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN
```

También puedes [comprobar si un enlace se ha abierto](/docs/apis/profiles/get-link-session) y
[revocarlo](/docs/apis/profiles/revoke-link-session) antes de que caduque.

<Note>
  Este vídeo de un minuto muestra cómo crear un enlace. Se grabó antes de las link sessions, así que
  todavía muestra el envío de una Private Key — ese paso ya no es necesario, y todo lo demás que
  muestra no ha cambiado.

  <div class="video-container">
    <iframe width="380" height="200" src="https://www.youtube.com/embed/JI232HBWHWc" title="Create a linking URL" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" />
  </div>
</Note>

<h3 id="sending-the-linking-url">
  Enviar la URL de vinculación
</h3>

<Warning>
  Una URL de vinculación inicia la sesión de tu usuario en su perfil, así que trátala como una
  contraseña. Envíala por un canal de confianza, no la registres en logs y no la pases a terceros.
  Sigue siendo válida durante toda su ventana, así que una recarga o un reintento de OAuth
  funcionan — pero envía cada enlace a un solo usuario y crea un enlace distinto por persona.
</Warning>

### Abrir la URL de vinculación

Ábrela en una nueva pestaña del navegador, una nueva ventana o un view controller. Puedes controlar
el [cierre o la redirección](/docs/multiple-users/api-integration-business#opening-&-closing-the-social-linking-url)
de esa ventana.

<Note>
  Las redes sociales no permiten abrir la página de vinculación alojada dentro de un iFrame, ni
  ofuscar el origen de partner autorizado `profile.ayrshare.com`. Si quieres que la vinculación
  ocurra dentro de tu propia página, para eso existe el [widget incrustado](/docs/multiple-users/connect-widget):
  sus frames se sirven desde un origen de Ayrshare y son la forma admitida de hacerlo.
</Note>

### Saber cuándo ha terminado

Dos señales, y puedes usar cualquiera de las dos:

* **[Eventos de finalización de vinculación](/docs/multiple-users/link-completion-events)** — establece un
  `origin` al crear el enlace y la ventana de vinculación envía `connect:success`, `connect:error` y
  `connect:cancelled` a tu página según ocurren. Sin polling.
* **[Obtener una Link Session](/docs/apis/profiles/get-link-session)** — informa de `completedAt`,
  `lastCompletedAt` y `completedNetworks`. Esta es la señal para apps nativas, y la única para
  Telegram, que completa fuera de banda.

<h2 id="jwt-expires-in">
  Caducidad del enlace
</h2>

Un enlace es válido durante **5 minutos** por defecto. Pasado ese tiempo, crea uno nuevo.

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

Con el Max Pack, establece `expiresIn` en minutos para ampliar esa ventana — hasta **2880 minutos
(48 horas)**, que es el máximo que acepta la API:

```json Expires In theme={"system"}
{
  "expiresIn": 30
}
```

Una ventana más larga es lo que hace práctico [enviar el enlace por correo](#connect-accounts-email):
un usuario que reconecta una cuenta caída puede ir directamente desde tu correo hasta la red, sin
visitar antes tu aplicación.

<Warning>
  Revisa con tu equipo de seguridad durante cuánto tiempo debe seguir vivo un enlace. Una ventana más
  larga es un periodo más largo en el que un enlace interceptado sigue funcionando. Si uno se filtra,
  puedes [revocarlo](/docs/apis/profiles/revoke-link-session) en lugar de esperar a que caduque.
</Warning>

## Profile Key

La `Profile-Key` identifica a qué User Profile corresponde el enlace. La encontrarás en el panel de
desarrollador de Ayrshare cambiando a ese perfil.

<Note>
  **La Private Key ya no se utiliza.** Los enlaces no se firman, así que no hay nada que leer de un
  archivo ni que pegar en tu código. El parámetro heredado `privateKey` se sigue aceptando y se
  ignora, por lo que las integraciones existentes siguen funcionando, y el archivo `private.key` de
  tu Integration Package puede quedar sin usar.
</Note>

## Cambiar de perfil

Si un perfil ya ha iniciado sesión, abrir el enlace de otro perfil no cambia de perfil — esto es
deliberado, y mantiene la experiencia rápida para un usuario que ya está ahí. Para forzar un cambio,
consulta
[Cierre de sesión automático de una sesión de perfil](/docs/multiple-users/api-integration-business#automatic-logout-of-a-profile-session).

<h2 id="instagram-link-method">
  Método de vinculación de Instagram
</h2>

Las cuentas de Instagram pueden vincularse de [dos maneras](/docs/dashboard/connect-social-accounts/instagram):
directamente con **Instagram Login**, o a través de una **Facebook Page conectada**. El flujo que se
inicia cuando un usuario hace clic en el botón de Instagram suele estar controlado por el ajuste
global de la cuenta [Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login).

El parámetro `instagramLinkMethod` del cuerpo sobrescribe ese ajuste para un único enlace:

| Valor | Flujo de vinculación de Instagram |
| - | - |
| `instagram` | Instagram Login directo. No requiere Facebook Page. |
| `facebook` | Vinculación a través de una Facebook Page conectada. |

```json Instagram Link Method theme={"system"}
{
  "instagramLinkMethod": "instagram"
}
```

La sobrescritura se aplica durante toda la vida de ese enlace, incluida la redirección de
autorización de Instagram/Facebook. Algunas cosas que debes saber:

* No cambia el ajuste global de tu cuenta ni afecta a ningún otro enlace.
* Si lo omites, se aplica el ajuste global de la cuenta, exactamente como antes.
* Un valor no válido devuelve `400` con la lista de valores válidos (`instagram`, `facebook`).
* Revisa las
  [diferencias de funcionalidad](/docs/multiple-users/manage-user-profiles#direct-instagram-login-vs-facebook-page-authentication)
  antes de elegir — algunas funcionalidades de Instagram, como la búsqueda por hashtags y las
  colaboraciones, solo están disponibles con la autenticación mediante Facebook Page.

<h2 id="connect-accounts-email">
  Connect Accounts Email
</h2>

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

Ayrshare puede enviar el enlace por correo a tu usuario por ti, para que llegue a su página de
vinculación sin visitar tu aplicación. Combínalo con un [`expiresIn`](#jwt-expires-in) más largo:
los cinco minutos por defecto rara vez sobreviven a una bandeja de entrada.

### Connect Accounts JSON

**Todos los campos dentro de `email` son obligatorios.** Si falta alguno, el envío falla.

```json Example Contact Email Request theme={"system"}
{
  "expiresIn": 60,
  "email": {
    "to": "john@user.com",
    "contactEmail": "support@mycompany.com",
    "company": "ACME",
    "termsUrl": "https://www.ayrshare.com/terms",
    "privacyUrl": "https://www.ayrshare.com/privacy"
  }
}
```

<Warning>
  `expiresIn` es un parámetro de **nivel superior**, no parte del objeto `email`. Anidado dentro de
  `email` se ignora, y tu usuario recibe un enlace que caduca en cinco minutos.
</Warning>

La respuesta informa del resultado en `emailSent`:

```json Example Contact Email Response theme={"system"}
{
  "status": "success",
  "sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
  "url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN",
  "expiresAt": "2026-09-02T09:03:26.838Z",
  "emailSent": true,
  "title": "Acme Client"
}
```

Si el correo no se puede enviar, la solicitud sigue teniendo éxito y recibes el enlace igualmente. La respuesta incluye `emailSent: false` y un objeto `emailError`, para que puedas enviar el enlace a tu usuario de otra forma:

```json Example Failed Email Response theme={"system"}
{
  "status": "success",
  "sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
  "url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN",
  "expiresAt": "2026-09-02T09:03:26.838Z",
  "emailSent": false,
  "emailError": {
    "code": 333,
    "message": "Email failed to send. Please confirm a valid email was provided."
  },
  "title": "Acme Client"
}
```

`emailError` aparece siempre que pediste un correo y no se envió: o bien el envío falló, o bien al objeto `email` le faltaba un campo obligatorio, y en ese caso `details` indica los campos que faltan. `emailSent: false` sin `emailError` significa que no se solicitó ningún correo. Si falta `to`, es distinto: se rechaza toda la solicitud con `code: 188`.

### Ejemplo de correo de Connect Accounts

Este es un ejemplo del correo que abre la página de vinculación social:

<img src="https://mintcdn.com/ayrshare-docs/Nmrhj2Gh7WSf62Bh/images/apis/profiles/jwt-email.webp?fit=max&auto=format&n=Nmrhj2Gh7WSf62Bh&q=85&s=fbe4ee86ca59c26a5bd5b289fda96b8b" alt="Connect Accounts email" width="563" class="center" data-path="images/apis/profiles/jwt-email.webp" />

El correo se envía desde:

`Social Connect Hub <connect@socialconnecthub.com>`

<h2 id="mobile-jwt">
  Apps móviles
</h2>

Abre la URL de vinculación en el **navegador del sistema**, nunca en un webview incrustado: Google
rechaza el inicio de sesión en uno con `disallowed_useragent`, y Meta lo bloquea directamente. Tu
usuario vería la página de error de la propia red, y nada en tu lado lo arregla.

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

Como una app nativa no tiene ventana de navegador a la que enviar eventos, obtén el resultado con
[Obtener una Link Session](/docs/apis/profiles/get-link-session) en su lugar. Establece `origin` con tu
esquema personalizado (`myapp://connected`) para que la página tenga una vía de vuelta a tu app.

<h3 id="mobile-code-examples">
  Ejemplos de código móvil
</h3>

Reemplaza `linkingURL` con la `url` devuelta por
[Crear una Link Session](/docs/apis/profiles/create-link-session).

<CodeGroup>
  ```swift Swift theme={"system"}
  import UIKit
  import SafariServices

  class ViewController: UIViewController, SFSafariViewControllerDelegate {

      var linkingURL = "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=acme"

      override func viewDidLoad() {
          super.viewDidLoad()
          setupButton()
      }

      func setupButton() {
          let button = UIButton(type: .system)
          button.frame = CGRect(x: (view.bounds.width - 200) / 2, y: (view.bounds.height - 50) / 2, width: 200, height: 50)
          button.setTitle("Open URL", for: .normal)
          button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
          view.addSubview(button)
      }

      @objc func buttonTapped() {
          openURLInInAppBrowser()
      }

      func openURLInInAppBrowser() {
          if let url = URL(string: linkingURL) {
              let safariVC = SFSafariViewController(url: url)
              safariVC.delegate = self
              present(safariVC, animated: true, completion: nil)
          }
      }

      // Optional: If you want to handle when the in-app browser is closed
      func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
          controller.dismiss(animated: true, completion: nil)
      }
  }
  ```

  ```dart Flutter theme={"system"}
  /** yaml dependencies
    dependencies:
      flutter:
        sdk: flutter
      url_launcher: ^6.2.1
  */

  import 'package:flutter/material.dart';
  import 'package:url_launcher/url_launcher.dart';

  void main() {
    runApp(MyApp());
  }

  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'URL Launcher Example',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      );
    }
  }

  class MyHomePage extends StatelessWidget {
    final String linkingURL = "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=acme";

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('URL Launcher Example'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              openURLInBrowser(context);
            },
            child: Text('Open URL'),
          ),
        ),
      );
    }

    void openURLInBrowser(BuildContext context) async {
      if (await canLaunch(linkingURL)) {
        await launch(linkingURL);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Could not launch $linkingURL'),
          ),
        );
      }
    }
  }
  ```

  ```jsx React Native theme={"system"}
  /**
  • Using the API provided by expo-web-browser,
  • which opens a URL in a modal browser window that shares cookies
  • with the system browser.

  • Learn more about expo: https://reactnative.dev/docs/environment-setup?guide=quickstart
  • and running the following command:
  • expo install expo-web-browser
  */

  import React from 'react';
  import { StyleSheet, Button, View } from 'react-native';
  import * as WebBrowser from 'expo-web-browser';

  export default function App() {
    const linkingURL = 'https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=acme';

    const openURLInBrowser = async () => {
      try {
        await WebBrowser.openBrowserAsync(linkingURL);
        // Optional: WebBrowser.openBrowserAsync returns a promise that resolves with an object containing
        // 'type' that can be 'cancelled' or 'dismissed'. You can use this to handle when the browser is closed.
      } catch (error) {
        console.error(error);
      }
    };

    return (
      <View style={styles.container}>
        <Button title="Open URL" onPress={openURLInBrowser} />
      </View>
    );
  }

  const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
    },
  });
  ```
</CodeGroup>

## Pruebas

Se **recomienda** crear primero un enlace en [Postman](/docs/testing/postman). Tu Integration
Package — en la página de API Key del Primary Profile del dashboard — incluye una configuración de
ejemplo para Postman. Impórtala, rellena tu Profile Key en el campo `profileKey` del *body* y pulsa
*Send*.

La configuración de ejemplo todavía rellena `privateKey` y `domain`. `privateKey` se ignora, y
puedes borrar `domain` salvo que tu cuenta tenga más de un dominio de vinculación.

También puedes [generar el código desde Postman](/docs/testing/postman#auto-generate-api-code-with-postman).

### Bubble.io

<Card title="Bubble linking URL" icon="link" href="/docs/packages-guides/bubble#generate-a-linking-url-in-bubble" horizontal />

## Legado: generateJWT

<Info>
  [Generar una URL de vinculación](/docs/apis/profiles/generate-jwt) (`generateJWT`) hace el mismo
  trabajo y está **deprecado** — totalmente soportado, sin fecha de retirada y sin cambios para los
  enlaces que ya hayas repartido. Su propia página documenta sus parámetros, incluidos los tres que
  ahora se aceptan y se ignoran.

  Ambos endpoints ejecutan el mismo validador, así que todo lo de esta página se aplica a cualquiera
  de los dos. La única diferencia que merece la pena conocer al migrar: `generateJWT` tolera tres
  cosas que Crear una Link Session rechaza — una red `allowedSocial` no reconocida, una sola mitad
  de las credenciales de X y un `redirect` que no sea una cadena.
</Info>


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