Skip to main content
The @utilsio/react/server package provides cryptographic signing functions for your backend. These functions prove to utilsio that requests come from your authorized application, enabling secure Superfluid subscription operations without exposing your app secret to the client.

How It Works: The Signing Flow

Understanding the authentication flow helps you implement signing correctly. Here’s the complete flow:
Why this is secure:
  • Your UTILSIO_APP_SECRET never leaves your server
  • Client can’t forge requests without the secret
  • Each request includes a fresh timestamp (prevents replay attacks)
  • HMAC-SHA256 ensures integrity of the request

Core Functions

deriveAppHashHex()

Derives a deterministic hex string from your app secret using scrypt key derivation. This is the HMAC key used for signing requests.
Why it exists: Scrypt is a memory-intensive KDF that prevents brute-force attacks on your secret. Deriving a key from your secret adds an extra security layer.
Parameters:
appSecret
string
required
Your app secret from the utilsio dashboard. This is sensitive and should never be exposed to the client.Keep this in UTILSIO_APP_SECRET environment variable on your backend.
salt
string
required
A hex-encoded salt string for key derivation. This is application-specific and stored in your utilsio dashboard.Keep this in UTILSIO_APP_SALT environment variable on your backend.
Returns: A 64-character hex string (256 bits, 32 bytes) Example:
src/utilsio/sign.ts
import { deriveAppHashHex } from "@utilsio/react/server";

// Derive once at module initialization (expensive operation)
const appHashHex = deriveAppHashHex({
  appSecret: process.env.UTILSIO_APP_SECRET!,
  salt: process.env.UTILSIO_APP_SALT!,
});

// Use appHashHex for all signing operations
export function signAllRequests(deviceId: string) {
  const signature = signRequest({
    appHashHex,
    deviceId,
    appId: process.env.NEXT_PUBLIC_UTILSIO_APP_ID!,
    timestamp: nowUnixSeconds(),
  });
  return signature;
}

signRequest()

Creates an HMAC-SHA256 signature for a request. This signature is sent to utilsio to prove the request is authorized. Parameters:
appHashHex
string
required
The derived app hash from deriveAppHashHex(). This is the HMAC key.
deviceId
string
required
The unique device identifier from the client. Identifies which device is making the request.This comes from the useUtilsio() hook’s deviceId property.
appId
string
required
Your utilsio App ID (public, safe to expose). This identifies your application to utilsio.
timestamp
number | string
required
Unix timestamp in seconds when the request is signed. Use nowUnixSeconds() to get the current time.This prevents replay attacks - signatures are only valid for a short time window.
additionalData
string
Optional additional data to include in the signature. Used to bind the signature to specific subscription operations.
  • For subscription creation: pass amountPerDay to bind the signature to that amount
  • For subscription cancellation: pass comma-separated subscription IDs
  • For basic requests: omit this parameter
This ensures the client can’t change the operation while reusing a signature.
Returns: A 64-character hex string (HMAC-SHA256 digest) Example:
src/utilsio/sign.ts
import { signRequest, nowUnixSeconds } from "@utilsio/react/server";

// In your /api/sign endpoint
const signature = signRequest({
  appHashHex,
  deviceId: "user-device-123",
  appId: process.env.NEXT_PUBLIC_UTILSIO_APP_ID!,
  timestamp: nowUnixSeconds(),
  additionalData: "0.033333", // Optional: for subscription creation
});

// signature = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"

nowUnixSeconds()

Returns the current Unix timestamp in seconds. This is a utility function for generating the timestamp parameter for signing.
Ensures consistent time reference across requests. Using Date.now() directly could cause issues if you’re not dividing by 1000. Still, it’s up to you to use Date.now() or this function.
Parameters: None Returns: Current Unix timestamp as an integer (seconds, not milliseconds) Example:
import { nowUnixSeconds } from "@utilsio/react/server";

const timestamp = nowUnixSeconds();
// timestamp = 1705355234 (example)

// Equivalent to:
const manualTimestamp = Math.floor(Date.now() / 1000);
// manualTimestamp = 1705355234
Use in signing:
const signature = signRequest({
  appHashHex,
  deviceId,
  appId,
  timestamp: nowUnixSeconds(), // Always current time
  additionalData,
});

buildSignatureMessage()

Builds the message that gets signed. Useful for debugging or implementing custom signing logic. Parameters:
deviceId
string
required
Device identifier
appId
string
required
Your app ID
timestamp
number | string
required
Unix timestamp in seconds
additionalData
string
Additional data to include in message
Returns: The message string that will be signed with HMAC-SHA256 Example:
import { buildSignatureMessage } from "@utilsio/react/server";

const message = buildSignatureMessage({
  deviceId: "abc123",
  appId: "my-app",
  timestamp: 1234567890,
  additionalData: "0.033333",
});

// message = "abc123-my-app-1234567890-0.033333"

// This message is then signed:
// signature = HMAC-SHA256(message, appHashHex)

Basic Signing Endpoint

app/api/sign/route.ts
import { NextRequest, NextResponse } from "next/server";
import {
  deriveAppHashHex,
  nowUnixSeconds,
  signRequest,
} from "@utilsio/react/server";

// Derive the app hash once at module load (expensive operation)
const appHashHex = deriveAppHashHex({
  appSecret: process.env.UTILSIO_APP_SECRET!,
  salt: process.env.UTILSIO_APP_SALT!,
});

const appId = process.env.NEXT_PUBLIC_UTILSIO_APP_ID!;

export async function POST(req: NextRequest) {
  try {
    // Parse request body
    const body = await req.json();
    const { deviceId, additionalData } = body as {
      deviceId: string;
      additionalData?: string;
    };

    // Validate required fields
    if (!deviceId) {
      return NextResponse.json(
        { error: "deviceId is required" },
        { status: 400 }
      );
    }

    // Generate signature
    const timestamp = nowUnixSeconds();
    const signature = signRequest({
      appHashHex,
      deviceId,
      appId,
      timestamp,
      additionalData,
    });

    // Return signed headers to client
    return NextResponse.json(
      {
        signature,
        timestamp: String(timestamp),
      },
      { status: 200 }
    );
  } catch (error) {
    console.error("Sign request error:", error);
    return NextResponse.json(
      {
        error:
          error instanceof Error ? error.message : "Failed to sign request",
      },
      { status: 500 }
    );
  }
}

Environment Variables

When setting up your signing endpoint, you’ll need these environment variables:
UTILSIO_APP_SECRET=your-app-secret
UTILSIO_APP_SALT=your-app-salt
NEXT_PUBLIC_UTILSIO_APP_ID=your-app-id
The UTILSIO_APP_SECRET and UTILSIO_APP_SALT are sensitive and should NEVER be prefixed with NEXT_PUBLIC_. These should only be used on the backend in your signing endpoint. Only NEXT_PUBLIC_UTILSIO_APP_ID is safe to expose in the browser.
Last modified on January 15, 2026