Troubleshooting
Common issues and solutions for the fdback.io SDK
"Invalid signature" Error
If you're getting INVALID_SIGNATURE errors, check these common causes:
1. Wrong message format
The signature must be computed over timestamp.body, NOT workspace_id.timestamp.body:
// ❌ Wrong
const message = `${workspaceId}.${timestamp}.${body}`;
// ✅ Correct
const message = `${timestamp}.${body}`;2. Wrong body fields
Only include email, name, and avatar. Do NOT include id or user_id:
// ❌ Wrong - includes "id" field
const body = JSON.stringify({
id: user.id,
email: user.email,
name: user.name,
});
// ✅ Correct
const body = JSON.stringify({
email: user.email,
name: user.name,
avatar: user.avatar,
});3. Wrong URL in response
The URL must be exactly https://app.fdback.io/api/auth/sdk-login:
// ❌ Wrong
{ url: "https://app.fdback.io/auth/login" }
// ✅ Correct
{ url: "https://app.fdback.io/api/auth/sdk-login" }4. Timestamp in seconds instead of milliseconds
Use milliseconds (13 digits), not seconds (10 digits):
// ❌ Wrong - seconds
const timestamp = Math.floor(Date.now() / 1000).toString();
// ✅ Correct - milliseconds
const timestamp = Date.now().toString();5. JSON with extra whitespace
Use compact JSON with no spaces:
// ❌ Wrong - has spaces
const body = JSON.stringify(user, null, 2);
// ✅ Correct - compact
const body = JSON.stringify(user);See Custom Backend Integration for complete examples.
"Signed login data has expired"
The signed data expires after 5 minutes. Generate fresh data before opening.
// ❌ Don't cache signed data
const signedData = await fdback.createSignedLoginData(user);
// ... time passes ...
fdback.open(signedData); // May fail if > 5 minutes
// ✅ Generate fresh data each time
const handleOpen = async () => {
const signedData = await fdback.createSignedLoginData(user);
fdback.open(signedData);
};"workspaceSecret should not be used in browser code"
You're initializing Fdback with a secret in client-side code. Remove the secret and use a backend API to sign requests.
// ❌ Don't do this in browser
const fdback = new Fdback({
workspaceId: "...",
workspaceSecret: "...", // Exposed to users!
});
// ✅ Use API route to sign
const fdback = new FdbackClient({
workspaceId: "...",
});
// Fetch signed data from your backend
const signedData = await fetch("/api/fdback/sign").then(r => r.json());
fdback.open(signedData);Build error: "This module cannot be imported from a Client Component"
You're importing @fdback.io/sdk/next in client code. These helpers are server-only.
// ❌ This fails in client components
"use client";
import { createSignHandler } from "@fdback.io/sdk/next"; // Error!
// ✅ Create a server action or API route instead
// app/api/fdback/sign/route.ts (server-only)
import { createSignHandler } from "@fdback.io/sdk/next";
export const POST = createSignHandler({ ... });
// Then call it from your client component
const signedData = await fetch("/api/fdback/sign", { method: "POST" });Popup blocked by browser
Browsers block popups that aren't triggered by direct user interaction.
// ❌ May be blocked - async before open
const handleClick = async () => {
const signedData = await fetchSignedData(); // Async call
fdback.open(signedData); // Blocked! Not direct user action
};
// ✅ Use the FeedbackButton component which handles this
import { FeedbackButton } from "@fdback.io/sdk/react";
<FeedbackButton user={user}>Feedback</FeedbackButton>CORS errors
Make sure your API route returns proper CORS headers if calling from a different domain:
// app/api/fdback/sign/route.ts
import { createSignHandler } from "@fdback.io/sdk/next";
const handler = createSignHandler({ ... });
export async function POST(request: Request) {
const response = await handler(request);
// Add CORS headers if needed
response.headers.set("Access-Control-Allow-Origin", "https://your-app.com");
return response;
}User not showing in fdback.io
Make sure you're passing the correct user data:
// ✅ Email is required
const signedData = await fdback.createSignedLoginData({
email: "user@example.com", // Required!
name: "John Doe", // Optional but recommended
avatar: "https://...", // Optional
});TypeScript errors with imports
Make sure you're using the correct subpath imports:
// Core SDK
import { Fdback, FdbackServer, FdbackClient } from "@fdback.io/sdk";
// Widget
import { widget } from "@fdback.io/sdk";
// React (client-side)
import { FdbackProvider, FeedbackButton, useFdback } from "@fdback.io/sdk/react";
// Next.js (server-side only)
import { createSignHandler, createSignAction } from "@fdback.io/sdk/next";Widget authentication not working
If the widget's signEndpoint isn't authenticating users:
1. Check your sign endpoint returns the correct format
// Your endpoint should return this structure:
{
url: string;
headers: {
"x-workspace-id": string;
"x-timestamp": string;
"x-signature": string;
};
body: string;
expiresAt: number;
}2. Verify cookies are being sent
The SDK calls your signEndpoint with credentials: 'include'. Make sure your endpoint accepts credentials:
// app/api/fdback/sign/route.ts
export const POST = createSignHandler({
// ...
});
// If using custom CORS headers, include:
// Access-Control-Allow-Credentials: true3. Check for non-200 responses
If your endpoint returns a non-200 status (e.g., user not logged in), the widget continues in anonymous mode. This is expected behavior.
4. Verify the workspace secret
Make sure FDBACK_WORKSPACE_SECRET matches the secret in your fdback.io dashboard.
Widget not appearing
Make sure you're calling widget.init() after the DOM is ready:
<script src="https://unpkg.com/@fdback.io/sdk"></script>
<script>
// Wait for DOM
document.addEventListener("DOMContentLoaded", () => {
FdbackSDK.widget.init({
workspaceId: "your-workspace-id",
});
});
</script>Or place the script at the end of <body>.
Seeing PostHog requests from fdback.io
If you notice network requests to PostHog's EU cloud (eu.i.posthog.com) coming from your fdback.io public board, these are from fdback.io itself — not from your application.
Fdback.io uses PostHog (EU cloud) for its own product analytics. These requests are completely separate from any PostHog instance you may have set up in your own app. No action is needed on your part.