Back to Blog

White-Label Facebook Integration with Late's Headless Mode

Build a completely branded Facebook connection experience for your users with Late's new headless OAuth mode. No more third-party branding in your integration flow.

By

Add social media scheduling and analytics to your app in minutes with Late's API.

Try Late free

Today we're excited to announce Headless Mode for Facebook connections—a new feature that lets you build completely custom, white-labeled Facebook page selection experiences using the Late API.

If you're building a SaaS product, agency tool, or any application where maintaining your brand identity throughout the user journey is critical, this feature is for you. Your users will never see the "Late" branding during the Facebook connection flow.

The Problem We Solved

Until now, when your users connected their Facebook accounts through Late's API, they would:

  1. Start OAuth on your app
  2. Authorize on Facebook
  3. Get redirected to Late's hosted page selector (with our branding)
  4. Finally redirect back to your app

For many of our customers, especially agencies and white-label platforms, that third step broke the user experience. Users would see "Late" branding and get confused about where they were in the flow.

With Headless Mode, you now have complete control over that page selection UI.

How Headless Mode Works

The concept is simple: instead of redirecting users to our hosted page selector, we redirect them directly to YOUR domain with all the OAuth data they need. You build the UI, and Late handles all the OAuth complexity behind the scenes.

┌─────────────────┐
│   Your App      │ 1. Start OAuth with &headless=true
│   (Backend)     │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   Late API      │ 2. Returns Facebook authUrl
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│    Facebook     │ 3. User authorizes
└────────┬────────┘
         │
         │ 4. Redirects to YOUR domain
         │    (not Late's domain!)
         ▼
┌─────────────────┐
│   Your App      │ 5. Your custom page selector
│   (Frontend)    │    with YOUR branding
└────────┬────────┘
         │
         │ 6. User selects page
         ▼
┌─────────────────┐
│   Late API      │ 7. Saves connection
└─────────────────┘

Getting Started: Add One Parameter

Enabling headless mode is incredibly simple. When you initiate the Facebook OAuth flow, just add &headless=true to your redirect URL:

// Standard mode (redirects to Late's hosted UI)
const response = await fetch(
  'https://getlate.dev/api/v1/connect/facebook?' +
  'profileId=YOUR_PROFILE_ID&' +
  'redirect_url=https://yourapp.com/success',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);

// Headless mode (redirects to YOUR UI)
const response = await fetch(
  'https://getlate.dev/api/v1/connect/facebook?' +
  'profileId=YOUR_PROFILE_ID&' +
  'redirect_url=https://yourapp.com/facebook/callback&' +
  'headless=true',  // ← Add this!
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);

const { authUrl } = await response.json();
// Redirect user to authUrl

That's it! Now when the user completes Facebook authorization, they'll be redirected to YOUR callback URL with all the OAuth data.

Building Your Custom Page Selector

Step 1: Handle the OAuth Callback

After Facebook OAuth, your user lands on your callback URL with these parameters:

https://yourapp.com/facebook/callback?
  profileId=507f1f77bcf86cd799439011&
  tempToken=EAAxxxx...&
  userProfile=%7B%22id%22%3A...&
  connect_token=a1b2c3d4...&
  platform=facebook&
  step=select_page

Parse these parameters in your frontend:

function FacebookCallback() {
  const [pages, setPages] = React.useState([]);
  
  React.useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    
    // Extract OAuth data
    const oauthData = {
      profileId: params.get('profileId'),
      tempToken: params.get('tempToken'),
      connectToken: params.get('connect_token'),
      userProfile: JSON.parse(
        decodeURIComponent(params.get('userProfile'))
      )
    };
    
    // Store for later use
    sessionStorage.setItem('late_oauth_data', JSON.stringify(oauthData));
    
    // Fetch available pages
    fetchPages(oauthData);
  }, []);
  
  return <YourCustomPageSelector pages={pages} />;
}

Step 2: Fetch Available Facebook Pages

Use Late's API to get the list of Facebook pages the user can manage:

async function fetchPages(oauthData) {
  const response = await fetch(
    `https://getlate.dev/api/v1/connect/facebook/select-page?` +
    `profileId=${oauthData.profileId}&` +
    `tempToken=${encodeURIComponent(oauthData.tempToken)}`,
    {
      headers: {
        'X-Connect-Token': oauthData.connectToken
      }
    }
  );
  
  const data = await response.json();
  return data.pages;
  
  // Returns:
  // {
  //   "pages": [
  //     {
  //       "id": "123456789",
  //       "name": "My Brand Page",
  //       "username": "mybrand",
  //       "category": "Brand",
  //       "access_token": "EAAyyyy..."
  //     }
  //   ]
  // }
}
Important: Use the X-Connect-Token header, not your regular API key. Late automatically generates short-lived connect tokens (valid for 15 minutes) for API users during OAuth flows.

Step 3: Display Pages with YOUR Branding

Now comes the fun part—design a page selector that matches your brand:

function YourBrandedPageSelector({ pages }) {
  return (
    <div className="your-app-container">
      {/* Your logo and branding */}
      <header className="your-header">
        <YourLogo />
        <h1>Connect Your Facebook Page</h1>
        <p>Choose which page to connect</p>
      </header>
      
      {/* Your custom design */}
      <div className="your-page-grid">
        {pages.map(page => (
          <PageCard
            key={page.id}
            name={page.name}
            username={page.username}
            category={page.category}
            onSelect={() => connectPage(page.id)}
          />
        ))}
      </div>
      
      {/* Your footer */}
      <footer className="your-footer">
        Need help? Contact support@yourapp.com
      </footer>
    </div>
  );
}

Step 4: Complete the Connection

When the user selects a page, send it back to Late's API:

async function connectPage(pageId) {
  const oauthData = JSON.parse(
    sessionStorage.getItem('late_oauth_data')
  );
  
  const response = await fetch(
    'https://getlate.dev/api/v1/connect/facebook/select-page',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Connect-Token': oauthData.connectToken
      },
      body: JSON.stringify({
        profileId: oauthData.profileId,
        pageId: pageId,
        tempToken: oauthData.tempToken,
        userProfile: oauthData.userProfile,
        redirect_url: 'https://yourapp.com/success'
      })
    }
  );
  
  const result = await response.json();
  
  // Connection saved! Redirect to success page
  window.location.href = result.redirect_url;
  // → https://yourapp.com/success?connected=facebook&profileId=...
}

Real Implementation Example

Here's a complete React component you can use as a starting point:

import { useState, useEffect } from 'react';

export default function FacebookPageSelector() {
  const [pages, setPages] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [oauthData, setOauthData] = useState(null);

  useEffect(() => {
    // Parse OAuth callback
    const params = new URLSearchParams(window.location.search);
    const data = {
      profileId: params.get('profileId'),
      tempToken: params.get('tempToken'),
      connectToken: params.get('connect_token'),
      userProfile: JSON.parse(decodeURIComponent(params.get('userProfile')))
    };
    setOauthData(data);
    
    // Fetch pages from Late API
    fetch(
      `https://getlate.dev/api/v1/connect/facebook/select-page?` +
      `profileId=${data.profileId}&tempToken=${encodeURIComponent(data.tempToken)}`,
      { headers: { 'X-Connect-Token': data.connectToken } }
    )
      .then(r => r.json())
      .then(result => {
        setPages(result.pages);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, []);

  async function handleSelectPage(pageId) {
    try {
      const response = await fetch(
        'https://getlate.dev/api/v1/connect/facebook/select-page',
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-Connect-Token': oauthData.connectToken
          },
          body: JSON.stringify({
            profileId: oauthData.profileId,
            pageId,
            tempToken: oauthData.tempToken,
            userProfile: oauthData.userProfile
          })
        }
      );
      
      const result = await response.json();
      window.location.href = '/integrations/success?platform=facebook';
    } catch (err) {
      setError(err.message);
    }
  }

  if (loading) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="animate-spin h-8 w-8 border-4 border-blue-500 border-t-transparent rounded-full" />
      </div>
    );
  }

  if (error) {
    return (
      <div className="max-w-md mx-auto mt-12 p-6 bg-red-50 border border-red-200 rounded-lg">
        <h3 className="text-red-800 font-bold mb-2">Connection Error</h3>
        <p className="text-red-600">{error}</p>
      </div>
    );
  }

  return (
    <div className="max-w-4xl mx-auto px-4 py-12">
      <header className="text-center mb-12">
        {/* Your logo here */}
        <h1 className="text-3xl font-bold mb-2">Connect Facebook Page</h1>
        <p className="text-gray-600">Choose which page to use for posting</p>
      </header>

      <div className="grid md:grid-cols-2 gap-4">
        {pages.map(page => (
          <button
            key={page.id}
            onClick={() => handleSelectPage(page.id)}
            className="p-6 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:shadow-lg transition-all text-left"
          >
            <h3 className="font-bold text-lg">{page.name}</h3>
            {page.username && (
              <p className="text-blue-600 text-sm mt-1">@{page.username}</p>
            )}
            {page.category && (
              <p className="text-gray-500 text-sm mt-2">{page.category}</p>
            )}
          </button>
        ))}
      </div>
    </div>
  );
}

Important: Security and Tokens

Late uses short-lived connect tokens specifically for headless OAuth flows:

  • Automatically generated when you initiate OAuth via API key (not browser session)
  • Valid for 15 minutes - enough time for users to select a page
  • Single-use - don't try to reuse them
  • Passed via X-Connect-Token header - not your regular API key

This design ensures that even if a connect token is intercepted, it expires quickly and can't be used to access your account.

Error Handling

Make sure to handle these common scenarios:

async function fetchPagesWithErrorHandling(oauthData) {
  try {
    const response = await fetch(
      `https://getlate.dev/api/v1/connect/facebook/select-page?` +
      `profileId=${oauthData.profileId}&tempToken=${encodeURIComponent(oauthData.tempToken)}`,
      { headers: { 'X-Connect-Token': oauthData.connectToken } }
    );
    
    if (!response.ok) {
      const error = await response.json();
      
      // Handle specific errors
      if (error.error?.includes('Invalid access token')) {
        throw new Error(
          'Facebook token expired. Please try connecting again.'
        );
      }
      
      if (error.error?.includes('no pages')) {
        throw new Error(
          'No Facebook pages found. You need to be an admin of at least one page.'
        );
      }
      
      throw new Error(error.error || 'Failed to fetch pages');
    }
    
    const data = await response.json();
    
    if (!data.pages || data.pages.length === 0) {
      throw new Error(
        'No pages available. Please create a Facebook page or get admin access to one.'
      );
    }
    
    return data.pages;
  } catch (err) {
    console.error('Error fetching Facebook pages:', err);
    throw err;
  }
}

Testing Your Implementation

Before going live, test these scenarios:

  1. Happy path: User with multiple pages selects one successfully
  2. No pages: User doesn't manage any Facebook pages
  3. OAuth cancellation: User clicks "Cancel" on Facebook
  4. Token expiry: User waits > 15 minutes before selecting
  5. Network errors: API calls fail temporarily

When to Use Headless Mode

Headless mode is perfect for:

  • White-label products: Your customers never see "Powered by Late"
  • Agency tools: Maintain your agency's brand throughout
  • Custom workflows: Add validation or business logic before page selection
  • Branded experiences: Match your app's exact design language

You might not need headless mode if:

  • You're okay with users seeing "Late" branding briefly
  • You want to minimize frontend development work
  • You're building an internal tool (not customer-facing)

What's Next?

We're bringing headless mode to more platforms:

  • Facebook - Available now!
  • 🚧 LinkedIn - Organization selection (coming soon)
  • 🚧 Pinterest - Board selection (coming soon)
  • 🚧 YouTube - Channel selection for brand accounts (coming soon)

Resources

Ready to implement headless mode? Check out these resources:

Pricing

Headless mode is included in all Late plans at no extra cost. Whether you're on our Free plan or Enterprise, you can use this feature.

Wrapping Up

Headless OAuth mode gives you the best of both worlds: Late's robust OAuth infrastructure combined with complete control over your user experience.

To get started:

  1. Add &headless=true to your OAuth initiation
  2. Build your custom page selector UI
  3. Call our selection endpoint to complete the connection

Questions? Email us at miki@getlate.dev - we'd love to hear how you're using headless mode!

“We built headless mode because our customers told us they needed it. If you have feedback or feature requests, please reach out—we're always listening.”

— Miki, Founder of Late

Build social media automation into your product

Add social media scheduling and analytics to your app in minutes with Late's API.

Built for developers. Loved by agencies. Trusted by 6,325 users.