fdback.iofdback.io

Custom Backend Integration

Implement the signing endpoint manually for non-Next.js frameworks

If you're not using Next.js, you can implement the signing endpoint manually in any backend language.

How It Works

  1. Your frontend calls your backend's sign endpoint
  2. Your backend creates an HMAC-SHA256 signature
  3. Your backend returns signed data to the frontend
  4. The frontend sends this to fdback.io for authentication

Signature Format

The HMAC-SHA256 signature is computed over this message:

{timestamp}.{body}

Where:

  • timestamp: Unix timestamp in milliseconds (e.g., "1704067200000")
  • body: JSON string of user data (compact, no extra spaces)

Common mistake: Do NOT include the workspace ID in the message. The format is just timestamp.body, not workspace_id.timestamp.body.

Required Body Fields

{
  "email": "user@example.com",
  "name": "John Doe",
  "avatar": "https://example.com/avatar.jpg"
}
FieldRequiredDescription
emailYesUser's email address
nameNoUser's display name
avatarNoURL to user's avatar image

Important: Do NOT include id, user_id, or any other fields. Only email, name, and avatar are accepted.

Response Format

Your endpoint must return this JSON structure:

{
  "url": "https://app.fdback.io/api/auth/sdk-login",
  "headers": {
    "x-workspace-id": "your-workspace-id",
    "x-timestamp": "1704067200000",
    "x-signature": "a1b2c3d4e5f6..."
  },
  "body": "{\"email\":\"user@example.com\",\"name\":\"John Doe\"}",
  "expiresAt": 1704067500000
}
FieldDescription
urlAlways https://app.fdback.io/api/auth/sdk-login
headers.x-workspace-idYour workspace ID from the dashboard
headers.x-timestampUnix timestamp in milliseconds when signed
headers.x-signatureHMAC-SHA256 hex signature (64 characters)
bodyJSON string of user data (same as what was signed)
expiresAtExpiration timestamp (recommended: 5 minutes from now)

Node.js / Express Example

const crypto = require('crypto');
const express = require('express');

const app = express();
app.use(express.json());

app.post('/api/fdback/sign', (req, res) => {
  // 1. Get your authenticated user (from session, JWT, etc.)
  const user = req.user; // Your auth system

  if (!user?.email) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // 2. Create the body (email required, name/avatar optional)
  const body = JSON.stringify({
    email: user.email,
    ...(user.name && { name: user.name }),
    ...(user.avatar && { avatar: user.avatar }),
  });

  // 3. Create timestamp (milliseconds)
  const timestamp = Date.now().toString();

  // 4. Create signature: HMAC-SHA256 of "timestamp.body"
  const message = `${timestamp}.${body}`;
  const signature = crypto
    .createHmac('sha256', process.env.FDBACK_WORKSPACE_SECRET)
    .update(message)
    .digest('hex');

  // 5. Return the signed data
  res.json({
    url: 'https://app.fdback.io/api/auth/sdk-login',
    headers: {
      'x-workspace-id': process.env.FDBACK_WORKSPACE_ID,
      'x-timestamp': timestamp,
      'x-signature': signature,
    },
    body: body,
    expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
  });
});

Python / Flask Example

import hmac
import hashlib
import json
import time
from flask import Flask, jsonify

app = Flask(__name__)

FDBACK_WORKSPACE_ID = 'your-workspace-id'
FDBACK_WORKSPACE_SECRET = 'your-workspace-secret'

@app.route('/api/fdback/sign', methods=['POST'])
def sign_fdback():
    # 1. Get your authenticated user
    user = get_current_user()  # Your auth system

    if not user or not user.email:
        return jsonify({'error': 'Unauthorized'}), 401

    # 2. Create body (compact JSON, no extra spaces)
    body_dict = {'email': user.email}
    if user.name:
        body_dict['name'] = user.name
    if user.avatar:
        body_dict['avatar'] = user.avatar

    body = json.dumps(body_dict, separators=(',', ':'))

    # 3. Create timestamp (milliseconds)
    timestamp = str(int(time.time() * 1000))

    # 4. Create signature
    message = f"{timestamp}.{body}"
    signature = hmac.new(
        FDBACK_WORKSPACE_SECRET.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # 5. Return signed data
    return jsonify({
        'url': 'https://app.fdback.io/api/auth/sdk-login',
        'headers': {
            'x-workspace-id': FDBACK_WORKSPACE_ID,
            'x-timestamp': timestamp,
            'x-signature': signature,
        },
        'body': body,
        'expiresAt': int(time.time() * 1000) + 5 * 60 * 1000,
    })

PHP Example

<?php
header('Content-Type: application/json');

// 1. Get your authenticated user
$user = get_current_user(); // Your auth system

if (!$user || !$user['email']) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

// 2. Create body
$bodyArray = ['email' => $user['email']];
if (!empty($user['name'])) {
    $bodyArray['name'] = $user['name'];
}
if (!empty($user['avatar'])) {
    $bodyArray['avatar'] = $user['avatar'];
}
$body = json_encode($bodyArray, JSON_UNESCAPED_SLASHES);

// 3. Create timestamp (milliseconds)
$timestamp = (string)(time() * 1000);

// 4. Create signature
$message = $timestamp . '.' . $body;
$signature = hash_hmac('sha256', $message, $_ENV['FDBACK_WORKSPACE_SECRET']);

// 5. Return signed data
echo json_encode([
    'url' => 'https://app.fdback.io/api/auth/sdk-login',
    'headers' => [
        'x-workspace-id' => $_ENV['FDBACK_WORKSPACE_ID'],
        'x-timestamp' => $timestamp,
        'x-signature' => $signature,
    ],
    'body' => $body,
    'expiresAt' => (time() * 1000) + (5 * 60 * 1000),
]);

Ruby / Rails Example

require 'openssl'
require 'json'

class FdbackController < ApplicationController
  def sign
    # 1. Get your authenticated user
    user = current_user

    unless user&.email
      return render json: { error: 'Unauthorized' }, status: :unauthorized
    end

    # 2. Create body
    body_hash = { email: user.email }
    body_hash[:name] = user.name if user.name.present?
    body_hash[:avatar] = user.avatar if user.avatar.present?
    body = body_hash.to_json

    # 3. Create timestamp (milliseconds)
    timestamp = (Time.now.to_f * 1000).to_i.to_s

    # 4. Create signature
    message = "#{timestamp}.#{body}"
    signature = OpenSSL::HMAC.hexdigest(
      'SHA256',
      ENV['FDBACK_WORKSPACE_SECRET'],
      message
    )

    # 5. Return signed data
    render json: {
      url: 'https://app.fdback.io/api/auth/sdk-login',
      headers: {
        'x-workspace-id': ENV['FDBACK_WORKSPACE_ID'],
        'x-timestamp': timestamp,
        'x-signature': signature
      },
      body: body,
      expiresAt: (Time.now.to_f * 1000).to_i + (5 * 60 * 1000)
    }
  end
end

Testing Your Signature

Use this script to verify your signature generation:

const crypto = require('crypto');

// Test data
const secret = 'your-workspace-secret';
const timestamp = '1704067200000';
const body = '{"email":"test@example.com","name":"Test User"}';

// Generate signature
const message = `${timestamp}.${body}`;
const signature = crypto
  .createHmac('sha256', secret)
  .update(message)
  .digest('hex');

console.log('Message:', message);
console.log('Signature:', signature);
console.log('Signature length:', signature.length); // Should be 64

Common Mistakes

MistakeSolution
Including workspace ID in messageMessage is timestamp.body, NOT workspace_id.timestamp.body
Adding id or user_id fieldOnly use email, name, avatar
Using seconds instead of millisecondsUse Date.now() or time() * 1000
Wrong URL in responseMust be https://app.fdback.io/api/auth/sdk-login
JSON with extra spacesUse compact JSON (no spaces after : or ,)
Signature in base64Signature must be hex-encoded (64 characters)

Security Notes

Never expose your workspace secret in client-side code. The secret should only exist on your server.

  1. Signatures expire after 5 minutes - Generate fresh data for each login attempt
  2. Use HTTPS for your sign endpoint
  3. Verify user authentication before signing - don't sign requests for unauthenticated users
  4. Use compact JSON - Ensure no extra whitespace in the body string

On this page