> ## Documentation Index
> Fetch the complete documentation index at: https://utilsio.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# React Server SDK Reference

> Complete reference for the utilsio React Server SDK. Learn how to sign requests, derive app hashes, and build a secure signing endpoint for Superfluid subscription management.

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:

```mermaid theme={null}
graph LR
    C[Client] -->|getAuthHeadersAction| B[Backend]
    B -->|signature + timestamp| C
    C -->|API request| A[utilsio API]
```

<Note>
  **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
</Note>

## 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.

<Tip>
  **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.
</Tip>

<AccordionGroup>
  <Accordion title="Parameters">
    <ParamField path="appSecret" type="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.
    </ParamField>

    <ParamField path="salt" type="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.
    </ParamField>
  </Accordion>

  <Accordion title="Return Values">
    A 64-character hex string (256 bits, 32 bytes)
  </Accordion>

  <Accordion title="Example">
    ```typescript src/utilsio/sign.ts theme={null}
    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;
    }
    ```
  </Accordion>
</AccordionGroup>

### `signRequest()`

Creates an HMAC-SHA256 signature for a request. This signature is sent to utilsio to prove the request is authorized.

<AccordionGroup>
  <Accordion title="Parameters">
    <ParamField path="appHashHex" type="string" required>
      The derived app hash from `deriveAppHashHex()`. This is the HMAC key.
    </ParamField>

    <ParamField path="deviceId" type="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.
    </ParamField>

    <ParamField path="appId" type="string" required>
      Your utilsio App ID (public, safe to expose). This identifies your application to utilsio.
    </ParamField>

    <ParamField path="timestamp" type="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.
    </ParamField>

    <ParamField path="additionalData" type="string">
      Optional additional data to include in the signature. Used to bind the signature to specific subscription operations.

      <AccordionGroup>
        <Accordion title="When to use">
          * For subscription creation: pass `amountPerDay` (in USD unit) to bind the signature to that amount
          * For subscription cancellation: pass comma-separated string containing `userId` and subscription IDs (sorted alphabetically)
          * For basic requests: omit this parameter
        </Accordion>
      </AccordionGroup>

      <Tip>
        This ensures the client can't change the operation while reusing a signature.
      </Tip>
    </ParamField>
  </Accordion>

  <Accordion title="Return Values">
    A 64-character hex string (HMAC-SHA256 digest)
  </Accordion>

  <Accordion title="Example">
    ```typescript app/actions.ts theme={null}
    import { signRequest } from "@utilsio/react/server";

    // In your server action
    const timestamp = Math.floor(Date.now() / 1000);
    const signature = signRequest({
      appHashHex,
      deviceId: "user-device-123",
      appId: process.env.NEXT_PUBLIC_UTILSIO_APP_ID!,
      timestamp,
      additionalData: "0.033333", // Optional: for subscription creation
    });

    // signature = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"
    ```
  </Accordion>
</AccordionGroup>

***

### `nowUnixSeconds()`

Returns the current Unix timestamp in seconds. This is a utility function for generating the `timestamp` parameter for signing.

<Note>
  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.
</Note>

<AccordionGroup>
  <Accordion title="Parameters">
    This function does not accept any parameters
  </Accordion>

  <Accordion title="Return Values">
    Current Unix timestamp as an integer (seconds, not milliseconds)
  </Accordion>

  <Accordion title="Example">
    ```typescript theme={null}
    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:**

    ```typescript theme={null}
    const signature = signRequest({
      appHashHex,
      deviceId,
      appId,
      timestamp: nowUnixSeconds(), // Always current time
      additionalData,
    });
    ```
  </Accordion>
</AccordionGroup>

***

### `buildSignatureMessage()`

Builds the message that gets signed. Useful for debugging or implementing custom signing logic.

<AccordionGroup>
  <Accordion title="Parameters">
    <ParamField path="deviceId" type="string" required>
      Device identifier
    </ParamField>

    <ParamField path="appId" type="string" required>
      Your app ID
    </ParamField>

    <ParamField path="timestamp" type="number | string" required>
      Unix timestamp in seconds
    </ParamField>

    <ParamField path="additionalData" type="string">
      Additional data to include in message
    </ParamField>
  </Accordion>

  <Accordion title="Return Values">
    The message string that will be signed with HMAC-SHA256
  </Accordion>

  <Accordion title="Example">
    ```typescript theme={null}
    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)
    ```
  </Accordion>
</AccordionGroup>

***

## Server Action Example

Next.js Server Actions provide a simpler and more secure way to sign requests compared to API routes. The server action pattern keeps your secret secure while being easier to use.

```typescript app/actions.ts theme={null}
"use server";

import { deriveAppHashHex, signRequest } from "@utilsio/react/server";

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

export async function getAuthHeadersAction(input: {
  deviceId: string;
  additionalData?: string;
}) {
  const timestamp = Math.floor(Date.now() / 1000);

  const signature = signRequest({
    appHashHex,
    deviceId: input.deviceId,
    appId: process.env.NEXT_PUBLIC_UTILSIO_APP_ID!,
    timestamp,
    additionalData: input.additionalData,
  });

  return { signature, timestamp: String(timestamp) };
}
```

**Usage in UtilsioProvider:**

```typescript app/layout.tsx theme={null}
import { UtilsioProvider } from "@utilsio/react/client";
import { getAuthHeadersAction } from "./actions";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <UtilsioProvider
          utilsioBaseUrl={process.env.NEXT_PUBLIC_UTILSIO_APP_URL!}
          appId={process.env.NEXT_PUBLIC_UTILSIO_APP_ID!}
          getAuthHeadersAction={getAuthHeadersAction}
        >
          {children}
        </UtilsioProvider>
      </body>
    </html>
  );
}
```

## Safari-Compatible Callback Endpoint

For Safari and browsers with strict third-party cookie blocking, you need a server-to-server callback endpoint that utilsio.dev can call to generate signatures.

<Note>
  **Why this is needed:** Safari blocks third-party cookies in iframes, preventing the SDK from reading `deviceId` from cookies. The callback endpoint allows utilsio.dev (running in first-party context) to request a signature from your server with the deviceId it can read.

  **Handles both flows:** This endpoint supports both subscription creation (where `additionalData` is `amountPerDay`) and cancellation (where `additionalData` is a sorted list containing `userId` and subscription IDs).
</Note>

```typescript app/api/signature-callback/route.ts theme={null}
import { NextRequest, NextResponse } from "next/server";
import { deriveAppHashHex, signRequest } from "@utilsio/react/server";

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

export async function POST(req: NextRequest) {
  try {
    // Verify request origin
    const origin = req.headers.get("X-utilsio-Origin");
    if (origin !== "utilsio.dev") {
      console.error("Invalid origin header:", origin);
      return NextResponse.json({ error: "Unauthorized origin" }, { status: 403 });
    }

    // Require HTTPS in production
    const isProduction = process.env.NODE_ENV === "production";
    const protocol = req.headers.get("x-forwarded-proto") || "http";

    if (isProduction && protocol !== "https") {
      console.error("HTTPS required in production");
      return NextResponse.json({ error: "HTTPS required" }, { status: 403 });
    }

    // Parse and validate request body
    const body = await req.json();
    const { deviceId, appId, additionalData, timestamp } = body as {
      deviceId: string;
      appId: string;
      additionalData: string;
      timestamp: number;
    };

    if (!deviceId || !appId || !additionalData || !timestamp) {
      return NextResponse.json(
        { error: "Missing required fields: deviceId, appId, additionalData, timestamp" },
        { status: 400 }
      );
    }

    // Validate timestamp is recent (within 60 seconds)
    const now = Math.floor(Date.now() / 1000);
    const timestampAge = Math.abs(now - timestamp);
    if (timestampAge > 60) {
      console.error("Timestamp too old or in future:", { timestamp, now, age: timestampAge });
      return NextResponse.json({ error: "Invalid timestamp" }, { status: 400 });
    }

    // Verify appId matches our app
    const expectedAppId = process.env.NEXT_PUBLIC_UTILSIO_APP_ID!;
    if (appId !== expectedAppId) {
      console.error("AppId mismatch:", { expected: expectedAppId, received: appId });
      return NextResponse.json({ error: "Invalid appId" }, { status: 403 });
    }

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

    console.log("Generated signature for server-to-server callback:", {
      deviceId,
      appId,
      timestamp,
    });

    return NextResponse.json({
      signature,
      timestamp,
    });
  } catch (error) {
    console.error("Signature callback error:", error);
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Failed to generate signature" },
      { status: 500 }
    );
  }
}
```

**Security features:**

* Validates `X-utilsio-Origin` header to ensure requests come from utilsio.dev
* Requires HTTPS in production
* Validates timestamp is recent (within 60 seconds)
* Verifies appId matches your application

<Tip>
  This endpoint is automatically included in the [Next.js template](https://github.com/utilsio/templates). If you're using the template, you don't need to create this file manually.
</Tip>

## Environment Variables

When setting up your signing endpoint, you'll need these environment variables:

```env theme={null}
UTILSIO_APP_SECRET=your-app-secret
UTILSIO_APP_SALT=your-app-salt
NEXT_PUBLIC_UTILSIO_APP_ID=your-app-id
```

<Warning>
  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.
</Warning>
