> For the complete documentation index, see [llms.txt](https://docs.replycorp.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.replycorp.io/connect-on-x-pixel-brand-setup-guide.md).

# Social Connect Pixel: Brand Setup Guide

How to install the ReplyCorp connect widget on your site (directly or via Google Tag Manager), what the browser requirements are, and how to debug a connect flow that "completes OAuth but doesn't stick". The widget connects **X** accounts via OAuth and, when enabled for your brand, **Instagram** accounts via DM verification (see section 2a).

### 1. Install the snippet

```html
<script async
  src="https://cdn.replycorp.io/pixel.js"
  data-brand-id="YOUR_ORG_SLUG"
  data-api-base="https://api.replycorp.io"></script>

<!-- Wherever the button should render -->
<div data-replycorp-connect></div>
```

For Subresource Integrity, use a pinned release (`pixel.v3.js` + `integrity` attribute); see `packages/pixel/README.md` and `published-versions.json` for the current hash.

When a visitor connects their X account, the widget dispatches a DOM event you can use for signup attribution:

```js
window.addEventListener("replycorp:connected", (e) => {
  // e.detail = { provider: "x" | "instagram", handle: "@username", userId: "<platform user id>" }
});
```

#### Optional: restrict which sites can embed your pixel

By default any site can embed your pixel. To lock it to your own domains, set **Allowed domains** in the admin panel (Connect widget tab): `example.com` matches exactly that host, `*.example.com` matches its subdomains, and an empty list allows any site. Unlisted origins get `403 origin_not_allowed` from the pixel endpoints and the widget logs a console warning instead of rendering.

### 2a. Instagram provider (optional)

If Instagram connect is enabled for your brand (ask ReplyCorp, or check your organization's pixel configuration — `enabledProviders` includes `"instagram"`; the default is X only), the widget offers Instagram alongside X:

1. The visitor picks Instagram and the widget shows a short keyword code (e.g. `RC-X7K2MP`) plus a direct DM link to a ReplyCorp-controlled Instagram account.
2. The visitor DMs the code; the widget polls until the DM is verified, then establishes the session and fires `replycorp:connected` with `provider: "instagram"` — same event, same cookie mechanics as the X flow.
3. For checkout attribution, the verification also issues the buyer a unique single-use discount code which your backend can send as `verificationCode` on the conversion — see **Identifying Instagram Buyers** in the [API documentation](/api-documentation.md).

There is no OAuth popup in this flow, so the COOP considerations below don't apply to Instagram; the cookie requirements (section on third-party cookie blocking) apply to both providers.

### 2. How the X OAuth flow works (and what can break it)

1. The widget opens a popup to ReplyCorp's API for the X OAuth flow.
2. When OAuth completes, the popup hands a one-time exchange token back to your page (via `postMessage`; if that is impossible, the widget's polling fallback claims the completed OAuth server-side — see COOP below).
3. Your page redeems the token; the API sets an `HttpOnly` session cookie (`rc_pixel_sess`, scoped to the API origin, `SameSite=None; Secure; Partitioned`).
4. The widget reads `GET /api/v1/pixel/session` with credentials and fires `replycorp:connected`.

Two browser mechanisms can interfere:

#### Cross-Origin-Opener-Policy (COOP)

If your site serves `Cross-Origin-Opener-Policy: same-origin`, the browser severs the popup's `window.opener`, so step 2's `postMessage` never arrives. **Pixel v3+ handles this automatically** with a claim-polling fallback, so no action is required — but if you want the faster postMessage path, serve:

```
Cross-Origin-Opener-Policy: same-origin-allow-popups
```

Pixel v1/v2 have no fallback: with strict COOP the popup closes silently and nothing happens. Upgrade the snippet if you see that behavior.

#### Third-party cookie blocking

`rc_pixel_sess` lives on `api.replycorp.io`, which is a third-party origin on your site. The cookie is set with the `Partitioned` (CHIPS) attribute, which current browsers accept even when they block unpartitioned third-party cookies:

| Browser                   | Works?                               |
| ------------------------- | ------------------------------------ |
| Chrome / Edge 114+        | Yes                                  |
| Chrome Incognito          | Yes (Partitioned cookies allowed)    |
| Firefox 131+              | Yes                                  |
| Safari 18.4+ (March 2025) | Yes                                  |
| Safari < 18.4             | No — use the first-party proxy below |

If a browser rejects the cookie, the widget logs a console warning (`OAuth ... succeeded but the session did not persist`).

### 3. First-party proxy (fallback for older browsers)

Serving the pixel API from your own origin makes the session cookie first-party and removes every cookie restriction. Reverse-proxy the exact path prefix `/api/v1/pixel` from your domain to `https://api.replycorp.io`, preserving request/response headers (especially `Set-Cookie`), then point the snippet at your own origin:

```html
<script async
  src="https://cdn.replycorp.io/pixel.js"
  data-brand-id="YOUR_ORG_SLUG"
  data-api-base="https://www.your-domain.com"></script>
```

Examples: nginx `location /api/v1/pixel/ { proxy_pass https://api.replycorp.io; ... }`, a Cloudflare Worker route, or a Next.js/Vercel rewrite for `/api/v1/pixel/:path*`.

Notes:

* The cookie is scoped to `Path=/api/v1/pixel`, so the proxy must be mounted at exactly that prefix.
* The OAuth popup itself still runs on ReplyCorp's origin; only the config/session endpoints need proxying.
* If your brand has a domain allowlist configured, your own domain must be on it — the proxied requests carry your origin.

### 4. Troubleshooting checklist

Work through these in order in the browser DevTools on your page:

1. **Does the widget render?** If not: check the console for `Missing data-brand-id` / `Connector disabled for brand`, and confirm `GET /api/v1/pixel/config` returns `enabled: true`. A `403 origin_not_allowed` (with a console warning naming your hostname) means this domain is missing from the brand's allowlist — add it under Admin → Connect widget → Allowed domains.
2. **After finishing OAuth in the popup, does `POST /api/v1/pixel/session/exchange` or `POST /api/v1/pixel/session/claim` appear in the Network tab?**
   * Neither appears and the popup closed silently: COOP severed the opener and you are on pixel v2 or older. Upgrade the pixel, or set `Cross-Origin-Opener-Policy: same-origin-allow-popups`.
3. **Exchange/claim returns 200 but the widget stays on "Connect"?** Third-party cookie blocked. Check Application → Cookies for `rc_pixel_sess` under the API origin, and look for the widget's `session did not persist` console warning. Fix: current browser (CHIPS support), or the first-party proxy above.
4. **`GET /api/v1/pixel/session` returns `connected: false` with the cookie present?** The `brandId` on the page and the one the user connected under may differ; the widget only accepts sessions for its own `data-brand-id`.
5. Local development: use the same hostname for page and API (`localhost` vs `127.0.0.1` are different hosts; `SameSite=Lax` cookies are not sent cross-host on plain HTTP).
