OAuth Connect
Let your users connect their Airbnb account to your app through a hosted, white-label OAuth page. You never handle tokens; we redirect the user back to your URL when they're done.
How it works
From your server, you create a Connect session. We give you a one-time URL on connect.repull.dev. Send your user there. We render a page styled with your brand, walk them through Airbnb's consent screen, exchange the OAuth code for tokens, and redirect them back to your app with ?status=connected appended.
- Your server calls
POST /v1/connect/airbnbwith aredirectUrl. - We return
oauthUrl— a URL onconnect.repull.dev. - Redirect your user to
oauthUrl. - We render your white-label Connect page → Airbnb consent → an account-confirmation screen (“Is this the right Airbnb account?”) → done.
- The user lands on your
redirectUrlwith status query params.
Same model as Stripe Connect or Plaid Link
Start a connect session
Server-to-server. Authenticate with your live API key. The session expires after 30 minutes if the user doesn't complete the flow.
curl -X POST 'https://api.repull.dev/v1/connect/airbnb' \
-H 'Authorization: Bearer sk_live_...' \
-H 'Content-Type: application/json' \
-d '{"redirectUrl": "https://yourapp.com/airbnb/connected"}'Response
{
"oauthUrl": "https://connect.repull.dev/cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp",
"provider": "airbnb",
"sessionId": "cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp",
"expiresAt": "2026-04-29T18:25:14.000Z"
}Handle the redirect back
When the OAuth flow completes (or fails), we redirect the user to your redirectUrl with these query params appended:
| Param | Value | Meaning |
|---|---|---|
| status | connected | Connection successful. hostId + accountId are also set. |
| status | cancelled | User declined or an error occurred. code may explain (access_denied, account_conflict, …). |
| status | expired | User took too long; the link expired before they completed authorization. |
| hostId | 123456789 | Airbnb host ID. Use this to query GET /v1/properties. |
| accountId | 42 | Repull-side connection ID. Stable across token refreshes. |
// Your /airbnb/connected handler
public function connected(Request $request)
{
$status = $request->query('status');
if ($status === 'connected') {
$hostId = $request->query('hostId'); // Airbnb host ID
$accountId = $request->query('accountId'); // Repull connection ID
// Associate $hostId with the currently authenticated user, kick off
// any post-connect work in your app (welcome email, dashboard nudge).
return redirect()->route('dashboard')->with('success', 'Airbnb connected!');
}
if ($status === 'cancelled') {
$code = $request->query('code', 'unknown');
$message = match($code) {
'wrong_account' => 'You authorized the wrong Airbnb account. Try again.',
'access_denied' => 'You declined to authorize Airbnb.',
'account_conflict' => 'This Airbnb account is already connected elsewhere.',
default => "Connection failed: {$code}",
};
return redirect()->route('dashboard.connections')->with('error', $message);
}
if ($status === 'expired') {
return redirect()->route('dashboard.connections')->with('error', 'The connect link expired. Please try again.');
}
return redirect()->route('dashboard.connections');
}Associating the connection with your user
When the user lands back on your redirectUrl, you'll usually want to record hostId against the user in your database. Two ways to know who that user is:
Option 1 — Auth-required redirect (simplest)
If your redirectUrlrequires login, the user's session is intact when they land back — the OAuth round-trip happens in the same browser session. Your usual auth()->user() / request.user works.
Option 2 — Encode user context into the redirectUrl
We preserve all your existing query params. When the user lands back, we only append &status=...&hostId=...&accountId=... to the URL you gave us:
// Generate
$signed = hash_hmac('sha256', $userId . ':' . time(), config('app.key'));
'redirectUrl' => route('airbnb.connected', ['from' => $userId, 'sig' => $signed]),
// Receive
public function connected(Request $request)
{
$userId = (int) $request->query('from');
$sig = $request->query('sig');
if (! $this->verify($userId, $sig)) abort(403);
$hostId = $request->query('hostId');
User::find($userId)->airbnbHosts()->syncWithoutDetaching([$hostId]);
// ...
}Why sign it?
from value and link an Airbnb host to the wrong user. A signed nonce or short-lived token makes this safe.Check connection status anytime
After the user is connected, query the canonical state via GET /v1/connect/airbnb:
curl 'https://api.repull.dev/v1/connect/airbnb' \
-H 'Authorization: Bearer sk_live_...'
# {
# "connected": true,
# "provider": "airbnb",
# "id": 42,
# "status": "active",
# "externalAccountId": "123456789",
# "createdAt": "2026-04-29T18:01:42.000Z"
# }Failure modes
All non-success outcomes redirect back to your redirectUrl with ?status=cancelled&code=<reason> (or ?status=expired):
- access_denied — user clicked “Cancel” on Airbnb's consent screen.
- wrong_account — user clicked “Use a different account” on the confirmation screen. They likely had the wrong Airbnb account logged in. Mint a fresh session and ask them to retry (in another browser tab where they're logged in to the right account).
- account_conflict — the Airbnb account is already connected to a different Repull workspace. Disconnect it from there first.
- token_exchange_failed — Airbnb rejected our token exchange. Usually transient; retry.
- activation_failed — DB write failed during the final activate step. Rare; transient; retry.
- expired (status, not code) — connect link is older than 30 minutes. Generate a new one.
Webhooks
connection.created via webhooks. Coming soon.Custom domain (Scale plan)
On the Scale plan, you can swap connect.repull.dev for your own subdomain — e.g. connect.yourapp.com — so the URL bar matches your brand end-to-end. Add a CNAME to cname.vercel-dns.com, paste the hostname in your dashboard, and we'll provision SSL automatically.