Developers
Basic linking, Widget installation, coding-agent integration and single sign-on, on one page.
Individual pages are the canonical, indexed versions — this aggregate is noindex so these pages do not compete as duplicate content.
Add FeedLog to your product with a coding agent
Open your project in a coding agent, then copy and send the prompt below. The agent will find the appropriate navigation location, add a link to your FeedLog portal and verify the change.
This is the simplest integration. It adds a direct link without Widget, SSO or server-side changes. You only need your FeedLog portal URL.
Copy prompt
Add a link to this product's FeedLog feedback portal. Work directly in the current repository: inspect the codebase, make the required changes, run the relevant checks, and verify the result. Do not stop at providing instructions or example code.
Communicate with me in the language I use. Follow all repository-level instructions, including AGENTS.md, CLAUDE.md, contribution guides, and existing coding conventions.
## Goal
Add one clear, user-facing way to open the product's FeedLog portal. This is a basic link integration:
- link directly to the FeedLog portal;
- do not install `@feedlog/widget`;
- do not add JWT signing or SSO;
- do not add a server-side redirect or proxy;
- do not change the application's authentication system.
The same direct link should work for signed-in and signed-out users.
## Inspect the project first
Before asking me where to edit, inspect the repository and determine:
- the frontend framework, version, package manager, and application entry points;
- how primary navigation, help menus, account menus, settings, and mobile navigation are organized;
- whether the product already has a Feedback, Feature requests, Support, Help, Roadmap, or Changelog link;
- how external URLs and public runtime configuration are managed;
- how navigation labels are translated;
- which reusable link, menu-item, button, and external-link components the project uses;
- the existing lint, typecheck, test, build, and browser-test commands.
Reuse the project's existing components, styles, configuration, localization, and external-link behavior. Do not redesign the navigation or refactor unrelated code.
Ask a focused question only when required information cannot be discovered from the repository.
## Determine the FeedLog URL
Search the repository and deployment configuration for an existing FeedLog portal URL. It should be an absolute `https://` URL in production and may use a FeedLog subdomain or the product's custom feedback domain.
If no reliable URL exists, ask me for the FeedLog portal URL. Do not guess it from the product name, repository name, email domain, or marketing-site URL.
The FeedLog URL is public and does not require a secret. Follow the project's existing public-configuration convention. If the project centralizes external URLs, add it there. If an environment variable is appropriate and no naming convention exists, use:
```env
FEEDLOG_URL=
```
Use any client-public prefix required by the framework. Do not add a server secret for this integration.
Avoid copying the literal URL into several components. Prefer the project's existing centralized configuration when the link appears in more than one responsive or localized navigation component.
## Choose the link location
Prefer an existing location where users already look for feedback or help, in this order when applicable:
1. an existing Feedback or Feature requests item;
2. an existing Help, Support, or resources menu;
3. the user or account menu;
4. the product's primary navigation or settings navigation.
Use the repository's information architecture rather than adding the link to every possible location. When desktop and mobile navigation are separate implementations of the same menu, keep both versions consistent.
If there are multiple equally plausible product areas and the correct placement is a product decision that cannot be inferred, ask me one focused placement question before changing navigation.
Do not replace an existing support channel, documentation link, roadmap, or changelog unless it is clearly intended to point to FeedLog. Preserve unrelated destinations.
## Label and behavior
Use the product's established terminology. Prefer an existing localized label. If no suitable term exists, use a concise label such as `Feedback` in English and add equivalent translations through the project's normal localization system.
Follow the application's established behavior for external links. If there is no convention, open FeedLog in a new tab so users do not lose in-progress work, and add the appropriate `rel="noopener noreferrer"` protection.
Use the project's external-link icon and accessible-name conventions when they exist. The link must remain keyboard accessible and understandable without relying only on an icon.
Do not add tracking parameters unless the project already has an approved analytics convention for external navigation.
## Handle incomplete repositories
This integration normally requires only the frontend application.
If the current repository does not contain the user-facing navigation, ask where the frontend application is located. Do not add the link to an unrelated backend, infrastructure, or documentation repository merely to make a change.
If the repository contains several applications, identify the customer-facing product before editing. Do not add the link to admin tools, internal dashboards, demo applications, or marketing sites unless the repository clearly indicates that one of those is the requested product.
## Verify the integration
Run the applicable repository checks, including lint, typecheck, tests, and production build. Do not fix unrelated pre-existing failures unless they block this integration; identify them separately.
Verify as many of these behaviors as the environment allows:
1. The link appears in the intended desktop and mobile location.
2. The label uses the project's localization system where applicable.
3. The destination is the configured FeedLog portal URL.
4. The link follows the project's external-navigation convention.
5. Keyboard navigation and the accessible name work correctly.
6. Existing navigation items still work.
7. The application builds and loads successfully.
Use the repository's approved browser-testing workflow when one exists. If browser verification is not possible, provide exact manual verification steps.
## Definition of done
Report the integration as complete only when:
- a user-facing FeedLog link has been added in the appropriate product location;
- the URL is configured according to project conventions;
- responsive and localized variants are consistent where applicable;
- the relevant automated checks pass;
- the destination and interaction have been verified, or any external limitation is clearly identified.
## Final report
At the end, report concisely:
1. the files changed;
2. where the FeedLog link appears;
3. how the FeedLog URL is configured;
4. whether the link opens in the current tab or a new tab;
5. the checks and tests run and their results;
6. anything incomplete or not verified.
Do not add Widget or SSO functionality unless I ask for it separately. Do not finish with only sample code or recommendations. Make the changes in the repository.The agent may ask for your FeedLog URL or where the customer-facing frontend is located if it cannot find either one in the repository.
Install the feedback widget
Embed FeedLog in your product so users can submit feedback without leaving your app. A user opens the launcher and describes what they need; AI converts the message into structured feedback on your board.
The integration requires one function call. The SDK is a loader: approximately 5 KB gzipped, with no runtime dependencies. It ships as ES modules with TypeScript declarations. The SDK renders the launcher, unread badge and panel; the feedback UI is hosted by FeedLog and loaded in an iframe. This architecture allows FeedLog to add widget features without requiring an SDK upgrade.
Want a coding agent to handle the integration?
Use the Widget integration prompt. Copy it into a coding agent that has access to your project, and it will inspect the codebase, implement the frontend and server changes, and verify the result.
Install
npm install @feedlog/widgetESM only
The package ships ES modules and type declarations and requires a bundler. It does not provide a <script src="…"> build or a global variable, so direct installation through a CMS code snippet is not supported.
Configure the widget
import { createWidget } from '@feedlog/widget'
createWidget({
baseUrl: 'https://acme.feedlog.ai',
auth: {
getToken: async () => {
const res = await fetch('/api/feedlog-token')
// A reachability problem, not a sign-out — see below.
if (!res.ok) throw new Error('token endpoint unavailable')
return (await res.json()).token ?? null
},
// Must resolve when the modal closes, not when it opens.
login: () => openYourSignInModal(),
},
theme: 'auto',
})createWidget is the package's only export. It has no return value, instance methods or events. The launcher opens the panel, and the panel's close button closes it. The widget manages this state internally, which allows its behaviour to change without requiring changes to your integration code.
Options
| Option | Required | Notes |
|---|---|---|
baseUrl | Yes | Your FeedLog address, e.g. https://acme.feedlog.ai. A custom domain works the same way. |
auth.getToken | Yes | () => Promise<string | null>. Reports the current sign-in state. Review the three possible outcomes below before implementing it. |
auth.login | No | () => void | Promise<void>. Opens your own sign-in UI when someone asks to sign in from inside the panel. |
theme | No | 'light', 'dark' or 'auto' (default). Follows the OS on auto. |
getToken has three outcomes, not two
getToken() reports the current sign-in state. FeedLog handles its three possible outcomes differently:
Your getToken() | FeedLog reads it as | What the widget does |
|---|---|---|
| resolves with a JWT | Signed in, as the email in that token | Exchanges it for a session and opens the panel as that user |
resolves with null | Signed out | Clears the cached session immediately and shows a sign-in prompt |
| rejects or throws | Temporary failure | Keeps the session; offers a retry, or leaves a working panel alone |
Never return null for a failure
null means that the user is signed out. Return it only when you know that no user is signed in. If the token route returns a 500 response, the request times out or fetch rejects, throw an error.
Returning null for a temporary failure clears the cached session. This can cause intermittent sign-outs with little diagnostic evidence because the original network error is no longer reported as an error.
getToken() runs when the panel is opened for the first time and on every subsequent open. This keeps the widget synchronized with your app's sign-in state, so the function should complete quickly. The SDK caches the FeedLog session obtained from the JWT; subsequent opens normally call your function without making another exchange request.
Opening your own sign-in UI
auth.login is optional. It is called when a user selects sign-in in the panel while getToken() returns null. It can open your existing sign-in UI, such as a modal, popup or full-page redirect to your identity provider. For a full-page redirect, the SDK stores a short-lived marker and reopens the panel when the user returns to the page, provided the round trip takes less than approximately five minutes.
A settled login() call does not indicate successful sign-in. It only tells the SDK that the interaction has ended. The SDK then calls getToken() again and uses that result to determine the sign-in state. When the user cancels, resolving and rejecting have the same effect. You do not need to return the outcome of the sign-in attempt.
An expired session does not call login() automatically. The SDK first retries getToken() without opening any UI because the user may have signed in in another tab. The sign-in UI opens only after the user explicitly selects sign-in inside the panel; it will not appear while the user is only reading the page.
Sign the token on your server
Your backend must sign the JWT returned by getToken() with an SSO secret from Developer → Single Sign-On. Use HS256 and the four supported claims, with exp set no more than 24 hours in the future:
// Server-side only — the secret must never reach the browser.
import jwt from 'jsonwebtoken'
export default handler(async (req) => {
const user = await currentUser(req) // your own session, however you read it
if (!user) return { token: null } // becomes `null` in getToken()
return {
token: jwt.sign(
{ email: user.email, name: user.name, picture: user.avatarUrl },
process.env.FEEDLOG_SSO_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' },
),
}
})The SDK posts the token to FeedLog, caches the returned session in localStorage under the signed-in email address, and exchanges the token again when the session expires. You do not call the exchange endpoint directly. The session is passed to the iframe in the URL fragment, keeping it out of access logs and Referer headers.
For secret creation and rotation, the full claim list and all exchange errors, see Single sign-on (JWT handoff). If browser handoff is already configured in your product, reuse the same signing code, secret and token.
Where to call it
Call createWidget() once per page on the client. It does nothing when window is unavailable, so it can be imported in a Nuxt or Next app. Place the call in a client-only lifecycle hook such as onMounted or useEffect, or in a .client plugin.
A second call logs a warning and returns without creating another widget. This prevents React StrictMode double mounts and HMR module reloads from adding a second launcher. Calling the function again does not update baseUrl or theme; reload the page to change either option.
DOM structure and appearance
The SDK adds one <div> with a shadow root to document.body. It contains the launcher, badge and panel. The shadow root prevents the host page's CSS resets from affecting the widget and prevents widget styles from affecting the host page; no additional z-index configuration is required. The launcher appears in the bottom-right corner. The panel is 400 px wide on desktop and becomes full-screen when the viewport is narrower than 520 px.
The brand colour comes from your FeedLog workspace, not from the SDK. Configure it in workspace settings. FeedLog also selects a readable foreground colour. theme is the SDK's only appearance option. It is passed to the iframe when the frame is created, before the first paint, so provide the final value in the createWidget() call. Resolving it later from a store or a media-query listener that runs after mount can cause the panel to display the wrong theme briefly. Use auto unless your app has its own light/dark toggle; auto follows the OS and updates when the OS setting changes.
The badge shows the number of unread replies. While the panel is closed, the SDK refreshes the count on page load and when the tab regains focus, subject to a 60-second cache. When the panel is open, the iframe updates the count and clears items as their threads are read. The displayed count is capped at 9+.
Enable the widget
Enable the widget in Settings → Widget. The SDK checks this setting before it renders. When the widget is disabled, it renders nothing and logs nothing. The setting applies to every site where the SDK is installed and does not require a new deployment of your product.
No domain allowlist is required. FeedLog does not maintain a registry of customer domains: the widget API accepts requests from any origin, and any site can frame the embed page. Authentication does not rely on cookies; the session is supplied as an explicit bearer token.
The widget supports current versions of Chrome, Firefox, Safari and Edge. The build targets ES2020 and uses Shadow DOM, fetch and Web Storage without polyfills. If Web Storage throws in private browsing mode, the SDK uses in-memory storage instead. The session cache then does not survive page reloads, resulting in one additional token exchange per page load.
Common errors you may encounter
Two launchers on the page. The guard covers a second call in the same page load, and prints createWidget() was already called; ignoring this call when it fires. Two launchers without this warning indicate two documents, such as an iframe or micro-frontend that loads the integration a second time.
Users are signed out intermittently. getToken() is returning null on a temporary failure instead of throwing. See the three outcomes above.
createWidget requires a baseUrl, or requires auth.getToken (TypeError). These errors are thrown synchronously and indicate that an option is missing or has the wrong type. getToken must be a function, not a promise or token string. baseUrl is not a valid URL means the value could not be parsed; include the scheme.
Nothing renders, and the console says could not load widget config. The SDK could not reach GET /api/widget/config at your baseUrl, so it cannot determine whether the widget is enabled and does not render the launcher. Open that URL directly. The same symptom can result from an incorrect baseUrl, a host that does not resolve to a workspace or an unavailable FeedLog instance.
Nothing renders, and there's no warning either. Either the widget is switched off in Settings → Widget, or the call only ever ran on the server. Confirm the client reached it by putting a console.log next to it.
The panel continues to show a loading spinner. The iframe loaded but did not report itself ready. Check the embed page request in the network panel. A 404 or 500 response renders as an empty frame rather than an error because FeedLog's error pages do not include the header that allows framing.
"Feedback could not be loaded." with a Try again button. getToken() threw, or the exchange failed. The status is in the console as Widget token exchange failed with status …; the reason is JSON in that response body, so read the request in the network panel. A 403 there means the widget is disabled, and a 400 usually indicates a token problem. The SSO page lists the exact messages.
A sign-in prompt inside the panel, for a user who is signed in to your app.getToken() is returning null. Most often the token route can't see your session cookie: a different subdomain, or a SameSite setting that drops it. Call the route from the browser console and check what it actually returns.
The badge is up to one minute out of date. The count is cached for 60 seconds while the panel is closed. Opening the panel refreshes the count.
Integrate the widget with a coding agent
Open your project in a coding agent, then copy and send the prompt below. The agent will inspect the repository, connect the widget to your existing authentication system, implement server-side JWT signing and verify the integration.
Do not add your FeedLog SSO secret to the prompt or paste it into the conversation. The agent will tell you where to configure it as a server-side secret.
Copy prompt
Integrate the FeedLog feedback widget into this project. Work directly in the current repository: inspect the codebase, make the required changes, run the relevant checks, and verify the integration as far as the available environment allows. Do not stop at providing instructions or example code.
Communicate with me in the language I use. Follow all repository-level instructions, including AGENTS.md, CLAUDE.md, contribution guides, and existing coding conventions.
Official documentation:
- Widget: https://help.feedlog.ai/developers/widget
- SSO and JWT signing: https://help.feedlog.ai/developers/sso
Read the official documentation before editing if network access is available. If it is unavailable, continue using the requirements below. If the current official documentation conflicts with this prompt, follow the documentation and mention the difference in your final report.
## Goal
Complete the full integration:
1. Install and initialize `@feedlog/widget` in the frontend.
2. Connect the widget to the application's existing sign-in state and sign-in UI.
3. Add a server-side endpoint that issues a FeedLog JWT for the current signed-in user.
4. Ensure signed-in users are identified correctly in FeedLog.
5. Ensure signed-out users can enter the application's existing sign-in flow.
6. Keep the FeedLog SSO secret out of browser code, client bundles, logs, and version control.
The widget uses the same JWT format as FeedLog browser SSO, but this task does not require a browser SSO redirect. Do not add a `/api/sso/jwt` handoff or other browser SSO flow unless the project already needs it for a separate reason.
## Inspect the project first
Before asking me for paths or implementation details, inspect the repository and determine:
- the frontend framework, version, package manager, and application entry points;
- where client-only startup code belongs;
- how the frontend reads the current sign-in state;
- how the server authenticates a request and reads the current user;
- where API routes, server actions, or backend handlers belong;
- how environment variables and deployment secrets are managed;
- whether this is a monorepo and where its frontend and backend applications live;
- the existing lint, typecheck, test, build, and browser-test commands.
Reuse the project's existing authentication, API, configuration, and error-handling patterns. Do not introduce a second authentication system or perform an unrelated authentication refactor.
Ask a focused question only when required information cannot be discovered from the repository.
## Handle incomplete repositories safely
The complete integration requires both browser code and a trusted server environment.
If the repository contains only frontend code:
- never sign the JWT in the browser;
- never place the FeedLog SSO secret in a public or client-side environment variable;
- complete only the frontend work that is safe to implement;
- then ask for the backend repository or an existing authenticated endpoint that can issue the FeedLog JWT;
- report the integration as incomplete until the server-side issuer is connected.
If the repository contains only backend code:
- implement the JWT signing function and authenticated token endpoint;
- add the relevant tests;
- ask where the frontend application is located;
- report that widget initialization is still pending.
If both sides are present, complete both without asking for information that the code already provides.
## Install and initialize the widget
Install `@feedlog/widget` with the repository's existing package manager. The package is ESM-only.
Use its public API:
```ts
import { createWidget } from '@feedlog/widget'
createWidget({
baseUrl: feedlogBaseUrl,
auth: {
getToken,
login,
},
theme: 'auto',
})
```
`createWidget` is the package's only export. It has no return value, instance methods, or events. Do not invent or depend on undocumented SDK APIs.
Initialize it only in the browser and once per page. Put the call in the framework's established client-only lifecycle or plugin location. Avoid duplicate initialization during route changes, hot module replacement, or React Strict Mode mounts.
Use the existing application theme when it can be determined reliably. Otherwise use `auto`.
The `baseUrl` must be the URL of the user's FeedLog instance, including the scheme. Reuse existing configuration when present. If it cannot be found, ask me for it. Do not guess a production URL.
## Implement `auth.getToken`
`auth.getToken()` must call an authenticated endpoint in this application. It must have three distinct outcomes:
- return a JWT string when a user is signed in;
- return `null` only when the application has confirmed that no user is signed in;
- throw an error when the request fails, times out, returns an unexpected response, or cannot be parsed.
Do not return `null` for a temporary failure. FeedLog treats `null` as an explicit sign-out and clears the cached widget session.
Do not sign the JWT in browser code.
## Implement `auth.login`
Connect `auth.login()` to the application's existing sign-in experience, such as its current sign-in route, modal, popup, or identity-provider redirect.
Preserve the current page or use the project's existing callback/redirect mechanism when appropriate. Do not create a second sign-in flow for the widget.
`login()` must not settle until the sign-in interaction has finished: for a modal or popup, resolve when it closes; for a full-page redirect, returning immediately is correct because the page navigates away. Returning as soon as the sign-in UI opens leaves the widget signed out until the user clicks sign-in a second time.
The completion of `login()` does not prove that sign-in succeeded. The widget calls `getToken()` again to determine the resulting state, so do not build the integration around a custom success value from `login()`.
## Add the server-side token endpoint
Add an endpoint using the project's existing server API conventions. It must:
1. Authenticate the request with the project's existing server-side session or middleware.
2. Read the user identity from that trusted server-side session.
3. Never trust an email, name, or picture supplied directly by the browser for JWT claims.
4. Return the application's normal signed-out response when there is no current user.
5. Sign and return a FeedLog JWT when a user is signed in.
6. Avoid caching one user's token for another user.
7. Never log the JWT, SSO secret, session cookie, or other credentials.
Use a response shape that fits the existing API conventions. A typical response is:
```json
{
"token": "<signed-jwt>"
}
```
For a signed-out request, the endpoint may return `{ "token": null }` or the project's established unauthenticated status. Make `getToken()` interpret that case as `null` while continuing to throw for temporary and unexpected failures.
## Sign the FeedLog JWT
Sign with HS256 on the server.
Supported claims:
- `email`: required; use the current authenticated user's valid email address;
- `exp`: required; Unix timestamp in seconds;
- `name`: optional;
- `picture`: optional; absolute avatar URL.
Use a one-hour lifetime by default. The expiration must not be more than 24 hours in the future.
For example, the payload should be equivalent to:
```ts
{
email: user.email,
name: user.name,
picture: user.avatarUrl,
exp: Math.floor(Date.now() / 1000) + 60 * 60,
}
```
Omit optional claims when the application does not have reliable values. Do not invent an email address when the current user has none; return a clear, safe error and report the limitation.
Reuse a suitable JWT library already present in the server project when possible. Otherwise add a maintained library compatible with the project's runtime. Do not implement cryptography manually.
## Configure the SSO secret safely
The FeedLog SSO secret is created in:
FeedLog Dashboard -> Developer -> Single Sign-On -> New Secret
Use the secret exactly as the UTF-8 string shown by FeedLog. Do not hex-decode or Base64-decode it before signing.
First check whether an appropriate server-side secret variable already exists, without printing its value. Follow the project's naming convention. If no convention exists, use:
```env
FEEDLOG_BASE_URL=
FEEDLOG_SSO_SECRET=
```
Only the base URL may be exposed to the browser when necessary. `FEEDLOG_SSO_SECRET` must remain server-only and must not use a public prefix such as `VITE_`, `NEXT_PUBLIC_`, `NUXT_PUBLIC_`, or `PUBLIC_`.
If the secret is not configured:
- add the server-side environment-variable declaration and an empty placeholder to the appropriate example environment file;
- do not generate a replacement secret;
- do not ask me to paste the real secret into chat;
- tell me how to create it in FeedLog and place it in the project's local secret store and deployment platform;
- ask me only to confirm when it has been configured.
You may complete code changes, static checks, and tests with a safe test-only secret, but do not claim that the live authentication flow was verified until the real deployment secret is configured. Never put a real secret in a fixture, snapshot, command output, or test log.
## FeedLog dashboard setting
The widget must be enabled in FeedLog Dashboard -> Settings -> Widget. If you cannot perform that external action, continue all repository work and list it as a required manual step.
## Error handling
Handle at least these cases using the project's existing error conventions:
- the user is signed out;
- the signed-in user has no valid email;
- the token endpoint request fails or times out;
- JWT signing fails;
- the FeedLog base URL is missing or invalid;
- the FeedLog SSO secret is missing;
- widget initialization fails.
Errors must not expose the JWT, secret, session cookie, or other credentials.
## Verify the integration
Run the applicable repository checks, including lint, typecheck, tests, and production build. Do not fix unrelated pre-existing failures unless they block this integration; identify them separately.
Add focused tests where the project has an established test setup. Verify as many of these behaviors as the environment allows:
1. A signed-in request receives an HS256 JWT with the correct `email` and `exp`.
2. The JWT expires no more than 24 hours in the future.
3. The user identity comes from the trusted server session, not browser-supplied claims.
4. A confirmed signed-out state makes `getToken()` return `null`.
5. A temporary endpoint or network failure makes `getToken()` throw instead of returning `null`.
6. From a signed-out state, using sign-in inside the widget and completing the application's sign-in flow leaves the widget signed in without a second click.
7. The widget initializes only once in the browser.
8. The SSO secret is absent from client code and the production client bundle.
9. The application builds and loads successfully.
10. When browser testing and a configured FeedLog instance are available, the widget opens and identifies the signed-in user correctly.
Use the repository's approved browser-testing workflow when one exists. If browser verification is not possible, provide exact manual verification steps.
## Definition of done
Report the integration as complete only when:
- the frontend widget is initialized;
- the server-side token endpoint is implemented;
- `getToken()` and `login()` use the existing authentication system;
- the secret remains server-only;
- the relevant automated checks pass;
- live verification is complete, or any external configuration that prevents it is clearly identified.
## Final report
At the end, report concisely:
1. the files changed;
2. where widget initialization lives;
3. where the token endpoint lives;
4. how the current user is read from the server session;
5. the environment-variable names used, without their values;
6. the checks and tests run and their results;
7. the remaining FeedLog Dashboard or deployment steps;
8. anything incomplete or not verified.
Do not finish with only sample code or recommendations. Make the changes in the repository.The agent may ask for your FeedLog URL or for the location of a missing frontend or backend repository. You will also need permission to create an SSO secret and enable the widget in the FeedLog Dashboard.
Single sign-on (JWT handoff)
Users who are already signed in to your product shouldn't have to sign in again to leave feedback. With FeedLog SSO, your backend signs a short-lived JWT with a secret that stays on your server. FeedLog uses the identity in that token to sign the user in.
This integration doesn't require directory sync, SAML metadata, or a registered callback URL.
Want a coding agent to handle the integration?
Use the SSO integration prompt. Copy it into a coding agent that has access to your project, and it will implement the product link, server-side handoff and signed-in and signed-out flows.
One secret, two integrations
The same signing secret and token format are used for both integrations.
| Browser handoff | Widget | |
|---|---|---|
| Endpoint | GET /api/sso/jwt | POST /api/widget/auth/exchange |
| Request | A redirect with the token in the query string | JSON { "jwt": "…" } |
| Response | A first-party session cookie, followed by a 302 redirect to return_to | JSON with a bearer token, its expiry, and the user's profile |
| Caller | The user's browser | The widget SDK |
After you configure SSO for either integration, you can use the same secret and token format for the other one.
Create a signing secret
Open Developer → Single Sign-On in the dashboard and click Create secret. Only workspace owners can access the secret list. Other members see an "Owners only" notice.
A secret consists of 64 hexadecimal characters. Add a label such as Production or Staging to identify it during rotation. Labels don't affect verification and can be edited. A workspace can hold up to five secrets. You can reveal and copy any of them again later; they are not shown only once.
The secret never leaves your server
Use the secret only to sign tokens in your backend. Anyone who has it can create a valid token for any email address and sign in as that user. Store it in a secret manager or server environment variable. Never include it in a frontend bundle or commit it to a repository.
Let signed-out visitors continue
SSO adds identity when your product already knows the user. It shouldn't make sign-in a requirement for visiting FeedLog.
Point the Feedback link in your product to a server route in your application. That route checks the current session when the link is followed:
- If the user is signed in, sign a JWT and redirect through FeedLog's SSO URL.
- If the user is signed out, redirect directly to the same FeedLog page without a JWT.
Don't open your product's sign-in screen for the second case. The visitor can use FeedLog anonymously and follow FeedLog's own sign-in flow if an action requires it. Also keep a failed session lookup separate from a confirmed sign-out; use your application's normal error handling instead of silently dropping the user's identity.
JWT requirements
FeedLog accepts only the HS256 algorithm. Use the UTF-8 bytes of the secret string exactly as it appears in the dashboard. Don't hex-decode or base64-decode the secret before signing. Decoding it changes the signing key and causes verification to fail.
| Claim | Required | Notes |
|---|---|---|
email | Yes | The identity key. Must contain @. Trimmed and lowercased before matching. |
exp | Yes | Expiry, in Unix seconds, as a number. 24 hours is the hard ceiling; an hour is a good default. |
name | No | Display name. Falls back to the email address when absent or blank. |
picture | No | Avatar URL. |
FeedLog doesn't read any other claims and doesn't support custom fields or kid. During verification, it tries each enabled secret until the signature matches. The token therefore doesn't need to identify which secret signed it.
The clock tolerance for exp is ±60 seconds. FeedLog rejects a token whose exp is more than 24 hours in the future; it doesn't reduce the value to the maximum.
import jwt from 'jsonwebtoken'
// Server-side only.
export async function feedbackRedirect(request, returnTo = '/') {
const baseUrl = new URL(process.env.FEEDLOG_BASE_URL)
const requestedUrl = new URL(returnTo, baseUrl)
const safeReturnTo = requestedUrl.origin === baseUrl.origin
? `${requestedUrl.pathname}${requestedUrl.search}${requestedUrl.hash}`
: '/'
// This function must distinguish a signed-out user from a session error.
const user = await currentUser(request)
if (!user) {
return Response.redirect(new URL(safeReturnTo, baseUrl), 302)
}
const token = jwt.sign(
{ email: user.email, name: user.name, picture: user.avatarUrl },
process.env.FEEDLOG_SSO_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' },
)
const handoffUrl = new URL('/api/sso/jwt', baseUrl)
handoffUrl.searchParams.set('jwt', token)
handoffUrl.searchParams.set('return_to', safeReturnTo)
return Response.redirect(handoffUrl, 302)
}Use a regular <a href> that points to this route in your application. Sign the token when the user follows the link, not when a long-lived or statically built page is rendered. This keeps the token short-lived and lets the same link choose the signed-in or anonymous branch at request time.
Browser handoff
GET /api/sso/jwt accepts two query parameters: jwt (required) and return_to (optional, defaults to /). On success, FeedLog sets a first-party session cookie on that host and responds with an HTTP 302 redirect to return_to.
return_to must stay on the same host. It can be a relative path such as /b/feature-requests or an absolute URL on the FeedLog host. FeedLog replaces any other value, including a protocol-relative URL such as //example.com, with /. It doesn't return an error for an invalid return_to. If users are sent to the home board instead of the requested page, check this parameter first.
FeedLog verifies the token against the secrets for the workspace resolved from the request host. Use the host for the workspace that the user should access.
Widget exchange
The widget SDK handles the exchange. Implement auth.getToken() and return the same signed JWT. The SDK posts it to /api/widget/auth/exchange, caches the returned bearer token by email address, and exchanges the JWT again after the bearer token expires. Your integration doesn't call this endpoint directly. See Install the feedback widget.
Because each exchange creates a session, the endpoint is limited to 30 requests per minute per IP. The SDK normally stays below this limit by caching bearer tokens. If the integration reaches the limit, check whether it is requesting and exchanging a newly signed JWT on every call.
Identity matching and permissions
FeedLog matches users globally by email address. The first token containing a new email address creates an end-user account without showing another login screen.
FeedLog reuses the existing account on later sign-ins. It reads name and picture only when it creates the account and doesn't refresh them on later sign-ins. Changes to these fields in your product therefore don't update the FeedLog profile.
If an email address changes in your product, FeedLog treats the new address as a new account. Posts, votes, and comments remain associated with the old address.
An SSO session is limited to end-user access. It can create feedback, vote, and comment. It can't open the dashboard, set or change a password, change the email address, edit the profile, or manage the workspace; those actions return 403. The session is also bound to the host that issued it.
Rotating a secret
Verification accepts any enabled secret, so you can rotate secrets without rejecting tokens signed by the old secret during deployment:
- Create a second secret and label it.
- Deploy your backend with the new secret.
- Disable the old one after all token issuers have switched to the new secret.
- Delete it a few days later.
Disabling a secret is reversible. If you re-enable it, unexpired tokens signed with it pass verification again. Deleting a secret is irreversible, and tokens signed with it stop passing verification immediately.
Signing tokens in local development
The production integration requires a backend that holds the secret. During local development, you can sign a token manually and add it to the URL:
SECRET=paste-a-secret-here node -e "const jwt=require('jsonwebtoken');\
console.log(jwt.sign({email:'[email protected]',name:'Dev User'},\
process.env.SECRET,{algorithm:'HS256',expiresIn:'1h'}))"Use the result in http://localhost:3000/api/sso/jwt?jwt=<token>. While developing the widget integration, you can instead return the same string from a hard-coded auth.getToken().
Development only
Sign tokens in a terminal or on a development server. Never sign them in browser code, because that would include the secret in the frontend bundle. A development secret can still create a valid token for any email address in its workspace. Use an isolated test workspace, or create a separate secret and disable it when you finish testing.
Common errors
Signed-out visitors are sent to your product's sign-in page. The application route behind the Feedback link is requiring authentication. Let the route read an optional session: when it confirms that no user is signed in, redirect directly to the requested FeedLog page without creating a JWT.
The user lands on the board signed out, with no error shown. Browser-handoff failures aren't displayed as raw errors. /api/sso/jwt redirects to a "We couldn't sign you in" page, which continues to the board after three seconds. Check the FeedLog server log for a line starting with [sso] login failed: to find the cause. The widget exchange endpoint returns its error reason as JSON.
Invalid or expired SSO token (400). The signature didn't match any enabled secret, or exp has passed. Both conditions return the same message. Check for a secret from the wrong environment, a disabled or deleted secret, a secret that was hex-decoded before signing, or a server clock offset greater than 60 seconds.
SSO token must carry an exp claim (400). exp is missing or isn't a number. Some libraries add this claim only when you pass an expiry option.
SSO token exp is too far in the future (400). exp exceeds the 24-hour maximum plus the 60-second tolerance. FeedLog rejects the token instead of reducing its expiry. For a link that must remain valid longer, such as a link in an email, point it to a redirect in your application and sign the token when the user follows that link.
SSO token must carry a valid email claim (400). The email claim is missing, isn't a string, or has no @ in it.
SSO is not configured for this organization (404). The workspace has no enabled secret. During rotation, verify that the new secret has been deployed before disabling the old one.
Organization not found (404). The request host doesn't resolve to a workspace. Check the domain used in the handoff or exchange request.
Widget is not enabled for this organization (403). The widget is disabled in workspace settings. This error isn't caused by the SSO configuration.
Too many token exchanges, try again shortly (429). More than 30 exchanges from one IP in a minute.
Widget token exchange failed with status 400. The SDK reports only the status code. Open the failed request in the browser network panel to read the message in the response body.
A user's name or avatar is out of date. FeedLog reads profile fields only when it creates the account. Later SSO sign-ins don't update them.
Integrate SSO with a coding agent
Open your project in a coding agent, then copy and send the prompt below. The agent will inspect your authentication system, implement the server-side JWT handoff, add the product link and verify both signed-in and signed-out navigation.
Do not add your FeedLog SSO secret to the prompt or paste it into the conversation. The agent will tell you where to configure it as a server-side secret.
Copy prompt
Integrate FeedLog single sign-on into this product. Work directly in the current repository: inspect the codebase, make the required changes, run the relevant checks, and verify the integration as far as the available environment allows. Do not stop at providing instructions or example code.
Communicate with me in the language I use. Follow all repository-level instructions, including AGENTS.md, CLAUDE.md, contribution guides, and existing coding conventions.
Official documentation:
- SSO and JWT signing: https://help.feedlog.ai/developers/sso
Read the official documentation before editing if network access is available. If it is unavailable, continue using the requirements below. If the current official documentation conflicts with this prompt, follow the documentation and mention the difference in your final report.
## Goal
Add a user-facing FeedLog link backed by a server-side redirect that carries the current user's identity only when the user is already signed in to this product.
The required behavior is:
- signed in: issue a short-lived FeedLog JWT and redirect through FeedLog's `/api/sso/jwt` endpoint;
- signed out: redirect directly to the same FeedLog destination without a JWT;
- authentication lookup failure: handle it as an operational error according to project conventions, not as a confirmed sign-out.
SSO is an identity enhancement, not a requirement for visiting FeedLog. Do not force a signed-out user to sign in, do not open the application's sign-in UI, and do not block anonymous access to FeedLog.
This task is for browser handoff. Do not install `@feedlog/widget` or implement the widget token-exchange flow unless I ask for it separately.
## Inspect the project first
Before asking me for paths or implementation details, inspect the repository and determine:
- the frontend and backend frameworks, versions, package manager, and application entry points;
- how the server authenticates a request and distinguishes a confirmed signed-out state from an authentication-system failure;
- how API routes, server actions, redirects, and middleware are organized;
- where the product's Feedback, Feature requests, Help, Support, Roadmap, or Changelog links live;
- how external URLs, environment variables, and deployment secrets are managed;
- how navigation labels are translated and how desktop and mobile navigation are kept consistent;
- the existing lint, typecheck, test, build, and browser-test commands.
Reuse the project's existing authentication, API, configuration, navigation, localization, and error-handling patterns. Do not introduce another authentication system or perform an unrelated authentication refactor.
Ask a focused question only when required information cannot be discovered from the repository.
## Handle incomplete repositories safely
The complete integration requires a trusted server environment. A frontend-only implementation cannot safely sign the JWT.
If the repository contains only frontend code:
- do not sign the JWT in the browser;
- do not put the FeedLog SSO secret in public or client-side configuration;
- identify the link and frontend changes that will be needed;
- ask for the backend repository or an existing authenticated FeedLog redirect endpoint;
- report the integration as incomplete until the server-side redirect is connected.
If the repository contains only backend code:
- implement the authenticated/anonymous redirect and JWT signing;
- add the relevant tests;
- ask where the customer-facing frontend navigation is located;
- report that the user-facing link is still pending.
If both sides are present, complete both without asking for information the code already provides.
If the product has no authentication system, implement a direct FeedLog link instead of creating a new login system. Report that SSO cannot add identity until the product has a trusted server-side user session.
## Add the product link and redirect
Prefer one application-owned server route, using the project's existing route naming conventions. The product's user-facing FeedLog link should point to this route rather than embedding a JWT in rendered HTML.
When the route is requested:
1. Resolve and validate the requested FeedLog destination.
2. Read the current user from the trusted server-side session.
3. If a user is signed in, sign a new JWT and redirect to the FeedLog SSO endpoint.
4. If the authentication system confirms there is no signed-in user, redirect directly to the FeedLog destination without `jwt` or other identity parameters.
5. If reading the session fails unexpectedly, use the project's normal operational-error handling. Do not silently classify the failure as a signed-out state.
Generate the JWT when the link is followed, not during a long-lived page build or static render. This keeps the token short-lived and allows the route to choose the signed-in or anonymous branch at request time.
Find and update an existing Feedback or Feature requests link when one is clearly intended for FeedLog. Otherwise add one appropriate user-facing link by following the repository's information architecture and reusable navigation components. Keep desktop, mobile, and localized variants consistent where applicable.
If multiple link locations are equally plausible and the choice cannot be inferred, ask me one focused placement question. Do not add the link everywhere.
## Preserve the destination
Support a destination such as `/`, `/b/feature-requests`, or another path on the configured FeedLog host when the product needs deep links.
The signed-in and signed-out branches must land on the same FeedLog destination:
- signed in: redirect to `FEEDLOG_BASE_URL/api/sso/jwt?jwt=...&return_to=...`;
- signed out: redirect directly to `FEEDLOG_BASE_URL/...` for that destination.
Treat all redirect input as untrusted. Allow only destinations on the configured FeedLog origin. Reject or replace external, protocol-relative, malformed, or otherwise unsafe values. Do not create an open redirect.
Use URL-building APIs rather than string concatenation so the JWT and destination are encoded correctly.
## Sign the FeedLog JWT
Sign with HS256 on the server.
Supported claims:
- `email`: required; use the current authenticated user's valid email address;
- `exp`: required; Unix timestamp in seconds;
- `name`: optional;
- `picture`: optional; absolute avatar URL.
Use a one-hour lifetime by default. The expiration must not be more than 24 hours in the future. FeedLog allows approximately 60 seconds of clock tolerance.
For example, the payload should be equivalent to:
```ts
{
email: user.email,
name: user.name,
picture: user.avatarUrl,
exp: Math.floor(Date.now() / 1000) + 60 * 60,
}
```
Omit optional claims when the application does not have reliable values. Do not invent an email address when the current user has none. Handle that case safely according to project conventions and report the limitation. Do not trust email, name, or picture values supplied directly by the browser.
Reuse a suitable JWT library already present in the server project when possible. Otherwise add a maintained library compatible with the project's runtime. Do not implement cryptography manually.
Never log the JWT, SSO secret, session cookie, or other credentials. Avoid placing the signed JWT in analytics events or application logs. The JWT will appear only in the short-lived redirect URL sent to FeedLog.
## Configure the FeedLog URL and secret
The FeedLog SSO secret is created in:
FeedLog Dashboard -> Developer -> Single Sign-On -> New Secret
Use the secret exactly as the UTF-8 string shown by FeedLog. Do not hex-decode or Base64-decode it before signing.
First check whether suitable server-side variables already exist, without printing their values. Follow the project's naming convention. If no convention exists, use:
```env
FEEDLOG_BASE_URL=
FEEDLOG_SSO_SECRET=
```
The base URL may be exposed to the browser if the project needs it. `FEEDLOG_SSO_SECRET` must remain server-only and must not use a public prefix such as `VITE_`, `NEXT_PUBLIC_`, `NUXT_PUBLIC_`, or `PUBLIC_`.
If the FeedLog base URL cannot be discovered from current configuration, ask me for it. Do not guess it.
If the secret is not configured:
- add the server-side environment-variable declaration and an empty placeholder to the appropriate example environment file;
- do not generate a replacement secret;
- do not ask me to paste the real secret into chat;
- tell me how to create it in FeedLog and place it in the project's local secret store and deployment platform;
- ask me only to confirm when it has been configured.
You may complete code changes, static checks, and tests with a safe test-only secret, but do not claim that the live SSO flow was verified until the real deployment secret is configured. Never put a real secret in a fixture, snapshot, command output, or test log.
## Error handling
Handle at least these cases using the project's existing error conventions:
- the user is confirmed to be signed out;
- session lookup fails unexpectedly;
- the signed-in user has no valid email;
- the FeedLog base URL is missing or invalid;
- the FeedLog SSO secret is missing;
- JWT signing fails;
- the destination is unsafe or malformed.
The confirmed signed-out case is not an error: redirect the visitor directly to FeedLog. Other failures must not expose credentials or be silently treated as anonymous access.
## Verify the integration
Run the applicable repository checks, including lint, typecheck, tests, and production build. Do not fix unrelated pre-existing failures unless they block this integration; identify them separately.
Add focused tests where the project has an established test setup. Verify as many of these behaviors as the environment allows:
1. A signed-in request produces an HS256 JWT with the correct `email` and `exp`.
2. The JWT expires no more than 24 hours in the future.
3. Identity claims come from the trusted server session, not browser input.
4. A signed-in request redirects through FeedLog `/api/sso/jwt` with the intended `return_to`.
5. A confirmed signed-out request redirects directly to the same FeedLog destination without a JWT.
6. A session lookup failure is not misclassified as a signed-out request.
7. External or protocol-relative destinations cannot create an open redirect.
8. The SSO secret is absent from client code and the production client bundle.
9. The user-facing link works in desktop, mobile, and localized navigation where applicable.
10. The application builds and loads successfully.
11. When browser testing and a configured FeedLog instance are available, signed-in and signed-out navigation both reach the intended FeedLog page, with identity present only for the signed-in user.
Use the repository's approved browser-testing workflow when one exists. If browser verification is not possible, provide exact manual verification steps for both branches.
## Definition of done
Report the integration as complete only when:
- the product contains an appropriate user-facing FeedLog link;
- the server redirect distinguishes signed-in, signed-out, and session-failure states;
- signed-in users are handed off with a valid JWT;
- signed-out users reach FeedLog without being forced to sign in;
- redirect targets are restricted to the configured FeedLog origin;
- the secret remains server-only;
- the relevant automated checks pass;
- live verification is complete, or any external configuration that prevents it is clearly identified.
## Final report
At the end, report concisely:
1. the files changed;
2. where the user-facing FeedLog link appears;
3. where the application-owned redirect route lives;
4. how the current user is read and how a confirmed sign-out is represented;
5. how the FeedLog destination is validated;
6. the environment-variable names used, without their values;
7. the checks and tests run and their results;
8. the remaining FeedLog Dashboard or deployment steps;
9. anything incomplete or not verified.
Do not finish with only sample code or recommendations. Make the changes in the repository.The agent may ask for your FeedLog URL or for the location of a missing frontend or backend repository. You will also need permission to create an SSO secret in the FeedLog Dashboard.