---
title: "Goodbye External Auth Pages, Hello Embedded UI: Introducing Ayrshare Connect."
description: "Ayrshare Connect puts social account linking inside your own dashboard. One script tag, your styling, ten events into your code, and the Ayrshare API keeps handling OAuth and token refresh."
canonical: https://www.ayrshare.com/blog/introducing-ayrshare-connect/
lastModified: 2026-10-08
pageType: blog
---

# Goodbye External Auth Pages, Hello Embedded UI: Introducing Ayrshare Connect.

Author: Antonios Minas

> Ayrshare Connect puts social account linking inside your own dashboard. One script tag, your styling, ten events into your code, and the Ayrshare API keeps handling OAuth and token refresh.

## TL;DR

Ayrshare Connect is now live. You can now let users link 14+ social accounts (including Instagram, LinkedIn, and TikTok) directly inside your app, eliminating the external redirects that cause user drop-off and disrupt your platform's native feel. By keeping the entire onboarding flow within your UI, you deliver a seamless, white-labeled experience while the [Ayrshare API](https://www.ayrshare.com/docs/multiple-users/connect-widget) quietly handles the OAuth callbacks, the token refresh and the per-network edge cases inside its own frames.

Ayrshare Connect lets your users link their social accounts (Instagram, LinkedIn, TikTok, etc.) directly inside your app's UI. We handle the OAuth callbacks, token refreshes, and API edge cases inside an iframe, while you keep the user on your domain.

## What Ayrshare Connect Is, and Why It Matters

Until now, linking a social account meant sending your user to a page Ayrshare hosts. You could add your logo and colours, but it was still our page on our domain, and your user left your product to use it.

Ayrshare Connect moves that step into your app. Our linking tiles render inside your layout, or behind your own button, and your user stays on your dashboard the whole time. The OAuth callbacks, token refresh and per-network quirks still run inside Ayrshare's frames, so you build nothing on that side.

### Why it matters

- **Better conversion.** Every hand-off to another domain loses people. Keeping the step on your page, with at most one popup from the network itself, removes the moment where a user wonders where they went and closes the tab.
- **More brand control.** Your fonts, colours, corner radius and product name, set through about 30 design tokens plus custom CSS. Even Meta's required hand-off screen carries your name and logo.
- **Less to build and maintain.** No popup handling, no OAuth callbacks, no session refresh and no per-network logic. When a network changes something on their end, the fix ships inside our frames, and you redeploy nothing.
- **Your code knows what happened.** Detailed events tell you when a link succeeded, failed or was cancelled, and why. No more polling to find out.

### What it looks like

There are three ways to put account linking in front of your users. Two of them are new with Ayrshare Connect, and the third is the hosted page you may already use.

![Three ways to link social accounts, side by side: Ayrshare Connect tiles embedded in a customer dashboard, a custom Connect button with a brief Ayrshare popup, and the hosted linking page in its own browser tab](https://cdn.sanity.io/images/ftd4n5q6/production/daa5901b2c3c76fd258dfcc69016968222110c01-3200x1096.png)

*Side by side: embedded frames (A) and your own button (B) keep the user in your app. The hosted linking page (C) is our page, with your logo.*

|  | A. Embedded frames | B. Your own button | C. Hosted linking page |
| --- | --- | --- | --- |
| Your user sees | Our tiles inside your layout. No popup until the network's own. | Your button, then one brief popup of ours. | A page we host, with your logo and colours. |
| Where they are | Your domain, the whole time. | Your domain, plus one popup. | Our domain. They leave your app to use it. |
| White-labelling | Strongest. Your page, your fonts and colours. | Strong. The popup is ours, but it inherits your appearance. | Weakest. Our page, your logo. |
| Choose it when | You have a dashboard with a row per network and want linking to happen in place. | You want your own button and styling, with no frames in your layout. | You want one link to hand out, or you onboard by email. |
| Requires | Business plan with the Max Pack. | Business plan with the Max Pack. | Business plan. No Max Pack needed. |

A and B are one integration, not two. A single `init` gives you both: mount frames where you want our tiles, and call `popup()` from your own button everywhere else. They share one session and report to the same event handlers.

Ayrshare Connect requires the Max Pack on the Business plan. If you have that, here is how to implement it.

## 1. The Setup

You need one script on the frontend and one endpoint on your backend.

First, drop the widget script into your page. Use the `v1` channel: it receives non-breaking patches automatically.

```markup
<script
  src="https://app.ayrshare.com/ayrshare-connect/v1/widget.js"
  crossorigin="anonymous"
></script>
```

Next, set up a backend endpoint to generate a session token. This keeps your Ayrshare API key off the client.

```javascript
// Your backend
app.get("/my-api/ayrshare-session", async (req, res) => {
  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": profileKeyFor(req.user), // Map this to your internal user
    },
    // The origin must exactly match the frontend environment for security
    body: JSON.stringify({ mode: "connect", origin: "https://app.example.com" }),
  });
  res.json(await response.json());
});
```

One session covers every network your account allows. If you mount 14 buttons, it still only costs one backend call.

The `origin` is the only security setting you need. Every frame checks the page embedding it against that value and stays blank anywhere else, so there is no allowlist to register. The session token signs your user into their profile, so treat it like a password.

The widget calls your endpoint again before the token expires, so a dashboard left open all day keeps working.

## 2. Integration Modes

You have three ways to render the connection flow, depending on how much control you want over the UI.

### Option A: Embedded (Mount)

This renders our connection buttons directly in your layout. Initialize the session, then mount the networks to specific DOM elements.

```javascript
const connect = AyrshareConnect.init({
  session: () => fetch("/my-api/ayrshare-session").then(r => r.json()),
});

// Render a single network
connect.mount("#instagram-slot", { network: "instagram" });

// Render multiple networks in one container
connect.mount("#the-rest", { networks: ["linkedin", "tiktok", "x"] });
```

The iframes automatically report their height and resize, so your layout won't break. Clicking a connected network allows the user to cleanly unlink it.

![A linked Bluesky tile inside a customer dashboard: the user clicks it, confirms Unlink, then links the account again in place](https://cdn.sanity.io/images/ftd4n5q6/production/ed2ec0ce88368fe7249e99aca67b4bbe8b08c8c1-1000x641.gif)

*Ayrshare Connect: click a connected tile to unlink it, confirm, and the tile resets. Click again to link the account back without leaving the page.*

### Option B: Custom Button (Popup)

If you want to use your own buttons, use the `popup` method. We still handle the logic, but the flow triggers via a brief popup.

```javascript
linkedInButton.addEventListener("click", () => {
  connect.popup({ network: "linkedin" });
});
```

**Important:** `connect.popup()` must run synchronously inside the click handler. Do not `await` anything before calling it, or the browser's popup blocker will intercept it.

Nothing needs awaiting anyway, because the session was created at `init`. `popup()` runs on that same session and reports to the same event handlers as your mounted frames.

### Option C: Direct Mode (No Script)

If you have a strict Content-Security-Policy (CSP), server-rendered pages, or a native mobile app, skip the script entirely.

Make the `/link-sessions` POST request for a specific network, extract the `url` from the response, and open it in a popup (or the system browser on mobile). You can then listen for `connect:success`, `connect:error`, or `connect:cancelled` window events.

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

Open the popup inside the click handler, with the same rule as Option B. Native apps should open the URL in the system browser, never an embedded webview, because the networks refuse to authenticate inside one. Then poll [Get a Link Session](/docs/apis/profiles/get-link-session) for the result. The [direct mode docs](/docs/multiple-users/connect-direct-mode) have the copy-paste listener and the per-network notes.

## 3. Styling and Theming

You can't inject CSS directly into a cross-origin iframe, so styling is passed as a configuration object during initialization. There are about 30 CSS custom properties available.

```javascript
AyrshareConnect.init({
  session: getSession,
  appearance: {
    "--ayr-connect-font-family": "Inter, system-ui, sans-serif",
    "--ayr-connect-surface-bg": "#EFF6FF",
    "--ayr-connect-surface-fg": "#0F2A5F",
    "--ayr-connect-accent": "#1D4ED8",
    "--ayr-connect-radius": "14px",
    "--ayr-connect-partner-name": "'Your App Name'", // Displays on the hand-off screen
  },
});
```

If you change the surface colour, change the text colour too, so the text stays readable. The tokens cover the common cases. For anything else, pass custom CSS through the `css` option, which is also where you add your logo to the hand-off screen.

![Switching the theme restyles every tile live](https://cdn.sanity.io/images/ftd4n5q6/production/439152dd008d15a6698303c354c64a02f58dc83d-1000x641.gif)

*Ayrshare Connect: one appearance change, and every tile restyles inside our frames.*

## 4. Event Handling (No Polling)

You no longer need to poll the API to check if an account linked successfully. The widget emits 10 distinct events, all including the relevant `network`.

Four of the ten are endings: `success`, `unlinked`, `error` and `cancelled`.

```javascript
// Update your UI when an account links successfully
connect.on("success", ({ network, displayName }) => {
  refreshRow(network, displayName);
});

// Handle failed attempts with specific error codes
connect.on("error", ({ network, code, message, reason }) => {
  // e.g., reason === "popupBlocked"
});

// Handle user bailing out of the flow
connect.on("cancelled", ({ network, reason }) => {
  // e.g., reason === "scopesDenied" or "userCancelled"
});
```

`cancelled` arrives with one of four reasons: `popupClosed`, `scopesDenied`, `userCancelled` or `superseded`. `error` reports `popupBlocked` when the browser refused the popup.

`click` fires before any linking work starts, in either direction (connect or unlink). It also counts clicks that a blocked popup went on to refuse, so it gives you the true number of attempts.

Listen to the `state` event to drive your UI reactively. It fires when a frame mounts and whenever the connection status changes (like a token expiring and requiring a re-link).

Because a blocked popup, a declined permission and a closed window each arrive with their own `reason`, your support team can tell a user exactly what happened instead of guessing.

You can also forward any event to your own analytics, such as Google Analytics. Send `click`, `success` and `cancelled`, and you can track the connection step like the rest of your onboarding. The [full event table](/docs/multiple-users/connect-widget#events) lists all ten.

## Meta's Extra Click Requirement

Facebook and Instagram (via FB Page) require the login flow to be initiated by a physical click on the page hosting the Meta SDK. Because of this, users linking Meta properties will see an intermediate screen with a second button.

We can't bypass this programmatically without violating Meta's rules, but the intermediate screen is fully white-labeled using your `--ayr-connect-partner-name` token to maintain trust.

That screen names your product, shows your logo, and tells the user that their password is never stored and the connection can be removed at any time. Embedded frames show it in place. With your own button, the popup shows it.

## Auth API Changes (JWT Deprecation)

- **Link Sessions replace signed JWTs:** The `generateJWT` endpoint and the Private Key requirement are deprecated.
- **Simpler setup:** [Create a Link Session](/docs/apis/profiles/create-link-session) needs only your API key and a Profile-Key. Both endpoints run the same validator, so moving off `generateJWT` is a parameter rename.
- **Backwards compatibility:** `generateJWT` will remain fully supported indefinitely, and the legacy `privateKey` parameter is now safely ignored if you send it.
- **Sessions report on themselves:** [Get a Link Session](/docs/apis/profiles/get-link-session) returns `completedAt`, `lastCompletedAt` and `completedNetworks`, and you can revoke a link before it expires.
- **Email links:** Link sessions generated with the Max Pack now have a maximum `expiresIn` window of 48 hours, and Ayrshare can send the connect email for you.

## Try It This Week

Ayrshare Connect is rolling out now. Read the [Connect Widget docs](/docs/multiple-users/connect-widget), create a session from your server, mount one slot, and see how it feels next to the flow your users have today. Then tell us what to change.

Not on the Ayrshare API yet? [Start a 28-day trial on the Launch plan](/pricing/) and build the connection step into your product first.

## Frequently asked questions

### Should I pin a version or track a channel?

Pin a version with its integrity hash if you rely on custom CSS or want to review every change. Otherwise track v1. Never combine integrity with a moving URL; the hash stops matching at our next release. Every version's hash is in manifest.json.

### Why does my session endpoint get called twice in development?

React Strict Mode runs effects mount, unmount, mount. The instance is created, destroyed and created again. With destroy() in your cleanup this is safe, and it costs nothing in production.

### What happens if the user just closes the popup?

You get cancelled with reason "popupClosed". Leave the row as it was. In direct mode, poll popup.closed with a short grace window before concluding anything, or a successful connection can be reported as cancelled.

### Can I put the hosted linking page in an iframe instead?

No. The social networks don't allow it, and they don't allow the profile.ayrshare.com origin to be hidden. The widget's frames are served from an Ayrshare origin and are the supported way to link in place.

### Which network finishes outside the browser?

Telegram. It shows a code rather than redirecting, and the connection completes when your user uses that code after the popup is gone. Poll Get a Link Session and watch completedNetworks.
