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

# Tổng quan Liên kết Mạng xã hội

> Cách người dùng của bạn kết nối tài khoản mạng xã hội của họ — ba bề mặt, phiên liên kết đứng sau tất cả, và các tùy chọn chung của chúng.

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} />

Người dùng của bạn tự kết nối các tài khoản mạng xã hội của chính họ — họ xác thực trực tiếp với
từng mạng, và bạn không bao giờ thấy hay lưu trữ thông tin đăng nhập của họ. Mọi thứ trên trang này
là về việc đưa họ đến khoảnh khắc đó và tìm hiểu kết quả ra sao.

Có một điều chung cho mọi lộ trình: một **phiên liên kết (link session)**, được tạo bằng
[Tạo một Link Session](/docs/apis/profiles/create-link-session) từ API key của bạn và một `Profile-Key`.
Không có gì được ký ở phía bạn và không có private key nào trong luồng.

## Ba cách Kết nối

Ba hình thức, và hai hình thức đầu là cùng một tích hợp. Hãy chọn theo từng luồng — chúng không
loại trừ lẫn nhau, và rất nhiều tích hợp dùng trang liên kết được lưu trữ cho onboarding và dùng
widget bên trong ứng dụng sau đó.

| Bề mặt | Người dùng của bạn thấy | White-label | Chọn khi |
| - | - | - | - |
| **Widget — khung nhúng** ([mount](/docs/multiple-users/connect-widget#mount-a-slot)) | các nút của chúng tôi hiển thị ngay trong bố cục của bạn; không có cửa sổ bật lên nào cho đến cửa sổ của chính mạng xã hội | **Mạnh nhất.** Trang của bạn, phông chữ và màu sắc của bạn, và người dùng của bạn không bao giờ rời khỏi nó | bạn có một dashboard với một hàng cho mỗi mạng và muốn việc liên kết diễn ra tại chỗ. Max Pack. |
| **Widget — nút của riêng bạn** ([popup](/docs/multiple-users/connect-widget#your-own-button)) | nút của bạn, rồi một cửa sổ bật lên cho mạng xã hội | **Mạnh.** Cửa sổ bật lên là của chúng tôi, nhưng nó xuất hiện ngắn và kế thừa giao diện của bạn | bạn muốn nút và kiểu dáng của riêng mình, và không có khung nào trong bố cục của bạn. Max Pack. |
| **Trang liên kết được lưu trữ** ([hướng dẫn](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | một trang do chúng tôi lưu trữ, mang logo, màu sắc và CSS tùy chỉnh của bạn | **Yếu nhất.** Đó là trang của chúng tôi, và người dùng của bạn rời khỏi trang của bạn để dùng nó | bạn muốn một liên kết duy nhất để gửi đi, hoặc bạn đang onboard qua email. Không phải xây dựng gì, không cần Max Pack. |

<Note>
  Hai hàng widget là **một tích hợp**, không phải hai. Một lần `init` duy nhất cho bạn cả hai: gắn
  các khung ở nơi bạn muốn có nút của chúng tôi, và gọi `popup()` từ nút của riêng bạn ở mọi nơi
  khác. Chúng dùng chung một phiên và báo cáo trên cùng các handler.

  [Chế độ trực tiếp (Direct Mode)](/docs/multiple-users/connect-direct-mode) là cùng cửa sổ bật lên đó
  **nhưng không có** script của chúng tôi — dành cho trang có Content-Security-Policy nghiêm ngặt,
  trang render phía máy chủ, hoặc ứng dụng gốc. Ở đó bạn tự mở và tự theo dõi cửa sổ bật lên.

  **Chưa chắc chọn gì?** Hãy bắt đầu với trang liên kết được lưu trữ. Nó không cần Max Pack và
  không cần tham số bổ sung, và là lộ trình nhanh nhất để có thứ gì đó hoạt động — chuyển sang
  widget sau này không thay đổi cách các phiên được tạo.
</Note>

## Tạo một Liên kết

Gọi [Tạo một Link Session](/docs/apis/profiles/create-link-session) với `Profile-Key` của người dùng
trong header. Với trang liên kết được lưu trữ, đó là toàn bộ yêu cầu:

```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 '{}'
```

Bạn nhận lại một `url` mang theo một token không minh bạch, có thời hạn ngắn:

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

Bạn cũng có thể [kiểm tra xem một liên kết đã được mở chưa](/docs/apis/profiles/get-link-session) và
[thu hồi nó](/docs/apis/profiles/revoke-link-session) trước khi hết hạn.

<Note>
  Video 1 phút này hướng dẫn việc tạo một liên kết. Video được ghi trước khi có link sessions, nên
  vẫn cho thấy việc gửi Private Key — bước đó không còn cần thiết, và mọi thứ khác trong video vẫn
  không thay đổi.

  <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">
  Gửi URL liên kết
</h3>

<Warning>
  URL liên kết sẽ đăng nhập người dùng của bạn vào profile của họ, nên hãy coi nó như một mật
  khẩu. Gửi qua kênh mà bạn tin cậy, không ghi vào log và không chuyển cho bên thứ ba. Nó vẫn
  dùng được trong suốt khoảng thời gian hiệu lực, nên tải lại hay thử lại OAuth đều được — nhưng
  chỉ gửi mỗi liên kết cho một người dùng, và tạo liên kết riêng cho từng người.
</Warning>

### Mở URL liên kết

Mở nó trong một tab trình duyệt mới, cửa sổ mới, hoặc view controller. Bạn có thể kiểm soát việc
[đóng hoặc chuyển hướng](/docs/multiple-users/api-integration-business#opening-&-closing-the-social-linking-url)
của cửa sổ đó.

<Note>
  Các mạng xã hội không cho phép mở trang liên kết được lưu trữ bên trong iFrame, hoặc che giấu
  origin đối tác đã được phê duyệt `profile.ayrshare.com`. Nếu bạn muốn việc liên kết diễn ra bên
  trong trang của riêng bạn, đó chính là mục đích của [widget nhúng](/docs/multiple-users/connect-widget):
  các khung của nó được phục vụ từ một origin của Ayrshare và là cách được hỗ trợ để làm điều đó.
</Note>

### Biết Khi nào Hoàn tất

Hai tín hiệu, và bạn có thể dùng tín hiệu nào cũng được:

* **[Sự kiện hoàn tất liên kết](/docs/multiple-users/link-completion-events)** — đặt một `origin` khi
  bạn tạo liên kết và cửa sổ liên kết sẽ gửi `connect:success`, `connect:error` và
  `connect:cancelled` đến trang của bạn ngay khi chúng xảy ra. Không cần polling.
* **[Lấy một Link Session](/docs/apis/profiles/get-link-session)** — báo cáo `completedAt`,
  `lastCompletedAt` và `completedNetworks`. Đây là tín hiệu cho ứng dụng gốc, và là tín hiệu duy
  nhất cho Telegram, vốn hoàn tất ngoài luồng.

<h2 id="jwt-expires-in">
  Thời hạn Liên kết
</h2>

Một liên kết có hiệu lực trong **5 phút** theo mặc định. Sau đó, hãy tạo liên kết mới.

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

Với Max Pack, hãy đặt `expiresIn` tính bằng phút để mở rộng khoảng thời gian đó — tối đa **2880
phút (48 giờ)**, là mức tối đa API chấp nhận:

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

Khoảng thời gian dài hơn là điều làm cho việc [gửi liên kết qua email](#connect-accounts-email) trở
nên thực tế — một người dùng kết nối lại tài khoản bị rớt có thể đi thẳng từ email của bạn đến mạng
xã hội, mà không cần vào ứng dụng của bạn trước.

<Warning>
  Hãy xem xét với đội bảo mật của bạn về việc một liên kết nên tồn tại bao lâu. Khoảng thời gian
  dài hơn là khoảng thời gian dài hơn mà một liên kết bị chặn bắt vẫn hoạt động. Nếu một liên kết
  lọt ra ngoài, bạn có thể [thu hồi nó](/docs/apis/profiles/revoke-link-session) thay vì chờ nó hết hạn.
</Warning>

## Profile Key

`Profile-Key` cho biết liên kết dành cho User Profile nào. Bạn tìm thấy nó trong bảng điều khiển
nhà phát triển của Ayrshare bằng cách chuyển sang profile đó.

<Note>
  **Private Key không còn được dùng.** Các liên kết không được ký, nên không có gì phải đọc từ tệp
  hay dán vào mã của bạn. Tham số cũ `privateKey` vẫn được chấp nhận và bị bỏ qua, nên các tích hợp
  hiện có tiếp tục hoạt động, và tệp `private.key` trong Integration Package của bạn có thể để
  không dùng.
</Note>

## Chuyển đổi Profile

Nếu một profile đã đăng nhập, việc mở liên kết của một profile khác sẽ không chuyển đổi profile —
điều đó là cố ý, và nó giữ trải nghiệm nhanh cho người dùng đã ở sẵn đó. Để buộc chuyển đổi, xem
[Tự động đăng xuất phiên Profile](/docs/multiple-users/api-integration-business#automatic-logout-of-a-profile-session).

<h2 id="instagram-link-method">
  Phương thức liên kết Instagram
</h2>

Tài khoản Instagram có thể được liên kết theo [hai cách](/docs/dashboard/connect-social-accounts/instagram):
trực tiếp với **Instagram Login**, hoặc thông qua **Facebook Page đã kết nối**. Luồng nào bắt đầu
khi người dùng nhấp vào nút Instagram thường được kiểm soát bởi cài đặt
[Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) toàn tài khoản.

Tham số body `instagramLinkMethod` ghi đè cài đặt đó cho một liên kết duy nhất:

| Giá trị | Luồng liên kết Instagram |
| - | - |
| `instagram` | Instagram Login trực tiếp. Không yêu cầu Facebook Page. |
| `facebook` | Liên kết thông qua Facebook Page đã kết nối. |

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

Ghi đè này áp dụng trong suốt vòng đời của liên kết đó, bao gồm cả trong quá trình chuyển hướng ủy
quyền Instagram/Facebook. Một vài điều cần biết:

* Nó không thay đổi cài đặt toàn tài khoản của bạn hoặc ảnh hưởng đến bất kỳ liên kết nào khác.
* Bỏ qua nó và cài đặt toàn tài khoản sẽ áp dụng, giống hệt như trước.
* Một giá trị không hợp lệ trả về `400` liệt kê các giá trị hợp lệ (`instagram`, `facebook`).
* Xem lại
  [sự khác biệt về tính năng](/docs/multiple-users/manage-user-profiles#direct-instagram-login-vs-facebook-page-authentication)
  trước khi chọn — một số tính năng Instagram, như tìm kiếm hashtag và cộng tác, chỉ khả dụng với
  xác thực Facebook Page.

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

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

Ayrshare có thể thay bạn gửi email liên kết đến người dùng của bạn, để họ đến được trang liên kết
của họ mà không cần vào ứng dụng của bạn. Hãy kết hợp nó với một [`expiresIn`](#jwt-expires-in) dài
hơn — 5 phút mặc định hiếm khi tồn tại nổi trong hộp thư đến.

### Connect Accounts JSON

**Mọi trường bên trong `email` đều bắt buộc.** Thiếu một trường sẽ làm lần gửi thất bại.

```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` là tham số **cấp cao nhất**, không phải một phần của đối tượng `email`. Nếu lồng bên
  trong `email`, nó bị bỏ qua, và người dùng của bạn nhận được một liên kết hết hạn sau 5 phút.
</Warning>

Phản hồi báo cáo kết quả trong `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"
}
```

Nếu không gửi được email, yêu cầu vẫn thành công và bạn vẫn nhận được liên kết. Phản hồi có `emailSent: false` cùng một đối tượng `emailError`, để bạn có thể gửi liên kết cho người dùng theo cách khác:

```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` xuất hiện bất cứ khi nào bạn yêu cầu gửi email nhưng email không được gửi: hoặc việc gửi thất bại, hoặc đối tượng `email` thiếu một trường bắt buộc, khi đó `details` sẽ nêu các trường bị thiếu. `emailSent: false` mà không có `emailError` nghĩa là không có email nào được yêu cầu. Thiếu `to` thì khác: toàn bộ yêu cầu sẽ bị từ chối với `code: 188`.

### Ví dụ Email Connect Accounts

Đây là một ví dụ về email mở trang liên kết mạng xã hội:

<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" />

Email sẽ đến từ địa chỉ:

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

<h2 id="mobile-jwt">
  Ứng dụng Mobile
</h2>

Hãy mở URL liên kết trong **trình duyệt hệ thống**, không bao giờ trong webview nhúng: Google từ
chối đăng nhập trong đó với `disallowed_useragent`, và Meta chặn hoàn toàn. Người dùng của bạn sẽ
thấy trang lỗi của chính mạng xã hội, và không có gì ở phía bạn khắc phục được điều đó.

* **iOS** — `ASWebAuthenticationSession`, hoặc `SFSafariViewController`.
* **Android** — Chrome Custom Tabs.

Vì ứng dụng gốc không có cửa sổ trình duyệt để nhận các sự kiện, hãy lấy kết quả từ
[Lấy một Link Session](/docs/apis/profiles/get-link-session) thay thế. Đặt `origin` là custom scheme
của bạn (`myapp://connected`) để trang có đường quay lại ứng dụng của bạn.

<h3 id="mobile-code-examples">
  Ví dụ mã Mobile
</h3>

Thay thế `linkingURL` bằng `url` được trả về từ
[Tạo một 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>

## Thử nghiệm

**Khuyến nghị** trước tiên hãy tạo một liên kết trong [Postman](/docs/testing/postman). Integration
Package của bạn — ở trang API Key của Primary Profile trên dashboard — bao gồm một cấu hình Postman
mẫu. Hãy import nó, điền Profile Key của bạn vào trường *body* `profileKey`, và nhấp *Send*.

Cấu hình mẫu vẫn điền trước `privateKey` và `domain`. `privateKey` bị bỏ qua, và bạn có thể xóa
`domain` trừ khi tài khoản của bạn có nhiều hơn một domain liên kết.

Bạn cũng có thể [tạo mã từ 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 />

## Legacy: generateJWT

<Info>
  [Tạo URL liên kết](/docs/apis/profiles/generate-jwt) (`generateJWT`) thực hiện cùng công việc và đã
  **ngừng khuyến nghị (deprecated)** — vẫn được hỗ trợ đầy đủ, không có ngày gỡ bỏ, và không thay
  đổi đối với các liên kết bạn đã phát đi. Trang riêng của nó ghi lại các tham số của nó, bao gồm
  ba tham số giờ đây được chấp nhận và bị bỏ qua.

  Cả hai endpoint chạy cùng một bộ kiểm tra, nên mọi thứ trên trang này áp dụng cho cả hai. Một
  khác biệt đáng biết khi bạn di chuyển: `generateJWT` dung thứ ba điều mà Tạo một Link Session từ
  chối — một mạng `allowedSocial` không nhận diện được, chỉ một nửa thông tin xác thực X, và
  `redirect` không phải chuỗi.
</Info>


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