Connect Widget
TL;DR
Mint a session with POST /v1/connect, send your user to the url that comes back, and read the result from the redirect or a postMessage. Nothing to install.
The widget lets your users connect their channel or PMS without you building an OAuth flow, a credential form, or a room-mapping screen. It is hosted by Repull: the picker, the per-channel handoff and the credential handling all run on connect.repull.dev, so your app holds no tokens and ships no connection UI.
There is no package to install
Mint a session
Call this from your server — it needs your API key, which must never reach the browser. redirectUrl is where the user lands when the flow finishes:
import { Repull } from '@repull/sdk'
const repull = new Repull({ apiKey: process.env.REPULL_API_KEY! })
const session = await repull.connect.createSession({
redirectUrl: 'https://your-app.com/connected',
state: 'user-123',
})You get back a one-time URL and the correlation values you will need on the way out:
{
"sessionId": "cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp",
"url": "https://connect.repull.dev/cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp",
"expiresAt": "2026-04-29T18:25:14.000Z",
"state": "user-123"
}Send the user there
Navigate to url, or open it in a popup. The picker shows a card for every channel the user can connect, and walks them through whichever one they pick — Airbnb consent, the Booking.com property claim and room mapping, or a PMS credential form.
// Full-page redirect — the simplest path. window.location.href = session.url // Or a popup, so your app stays on screen. window.open(session.url, 'repull-connect', 'width=900,height=700')
Read the result
A full-page redirect returns the user to your redirectUrl with status query params, and your server can confirm the connection from GET /v1/connect/{provider}. A popup instead posts a message back to window.opener and closes itself:
window.addEventListener('message', (event) => {
// Pin the origin. Anything else on the page can post to you.
if (event.origin !== 'https://connect.repull.dev') return
// Correlate, so a stale popup from an earlier attempt is ignored.
if (event.data?.sessionId !== session.sessionId) return
switch (event.data.type) {
case 'repull:connect:completed':
// event.data.provider, event.data.connectionId
break
case 'repull:connect:error':
console.error(event.data.error)
break
case 'repull:connect:close':
// Cancelled or expired.
break
}
})Narrow the picker
Pass allowedProviders to show only the channels that make sense for your product. The IDs come from GET /v1/connect/providers, which is public and needs no key:
const session = await repull.connect.createSession({
redirectUrl: 'https://your-app.com/connected',
allowedProviders: ['guesty', 'hostaway', 'lodgify'],
})If you already know which channel the user manages their listings on, skip the picker entirely: POST /v1/connect/{provider}mints a session that opens on that channel's screen.
Appearance and language
The hosted pages render with the logo, colors and trust signals set in your dashboard under Settings → Connect, so they read as part of your product rather than ours. On the Scale plan you can serve them from your own subdomain instead of connect.repull.dev.
Pass locale when minting the session to pin the language for the whole flow. English and French are supported today; an unknown code falls back to your workspace default.
The long version