Skip to main content
Private channels allow you to restrict app updates to specific users. This is useful for beta testing, early access programs, or paid tiers.

Overview

When a user requests an update from a private channel, your backend must:
  1. Verify the user has access to the requested channel
  2. Request an access token from the Overlayed API
  3. Return the token to the client
The client then uses this token to authenticate with the update server.

Client Setup

Configure the access token fetcher in your Electron main process:
overlay.updater.setAccessTokenFetcher(async (channel) => {
	const response = await fetch("https://your-api.com/access-token", {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${getUserSessionToken()}`,
		},
		body: JSON.stringify({ channel }),
	});

	if (!response.ok) {
		return null;
	}

	const data = await response.json();
	return data.access_token;
});
Handle token invalidation to move users back to the public channel:
overlay.updater.on("authTokenInvalid", async () => {
	// User lost access, force an update.
	// Overlayed will send them to the latest `stable` version.
	await overlay.updater.checkForUpdates();
	overlay.updater.quitAndInstall(true, true);
});

Server Implementation

Your server must call the Overlayed API to generate access tokens. The endpoint is:
POST https://api.overlayed.dev/v1/applications/{application_id}/channels/{channel_id}/tokens
Headers:
  • Authorization: Bearer {your_overlayed_api_key}
  • Content-Type: application/json
Body:
{
	"audience": "user_unique_id",
	"expires_in_minutes": 60
}
Response:
{
	"access_token": "eyJhbGciOiJIUzI1NiIs..."
}
app.post("/access-token", async (req, res) => {

    const { channel } = req.body;

    const userId = req.user?.id;

    // List of channels can be obtained from the Overlayed API
    const requestedChannel = channelsCache[channel];
    if (!requestedChannel) {
    	return res.status(404).json({ error: "Channel not found" });
    }

    if (!requestedChannel.private) {
    	return res.json({ access_token: null });
    }

    if (!userId) {
    	return res.status(401).json({ error: "Unauthorized" });
    }

    const userChannels = getUserChannels(userId);
    if (!userChannels.includes(channel)) {
    	return res.status(403).json({ error: "No access to this channel" });
    }

    const response = await fetch(
    	`https://api.overlayed.dev/v1/applications/${APPLICATION_ID}/channels/${requestedChannel.id}/tokens`,
    	{
    		method: "POST",
    		headers: {
    			Authorization: `Bearer ${OVERLAYED_API_KEY}`,
    			"Content-Type": "application/json",
    		},
    		body: JSON.stringify({
    			audience: userId,
    			expires_in_minutes: ACCESS_TOKEN_EXPIRATION_MINUTES,
    		}),
    	},
    );

    if (!response.ok) {
    	return res.status(500).json({ error: "Failed to generate token" });
    }

    const data = await response.json();
    return res.json({ access_token: data.access_token });

});

Security Considerations

  • Store your Overlayed API key securely. Never expose it to clients.
  • Validate user authentication before generating tokens.
  • Use short token expiration times (30-60 minutes recommended).
  • The audience field should be a unique identifier for the user. This is logged for analytics.