Request Hooks

The Descope SDK exposes a hooks configuration option that lets you intercept every HTTP request the SDK makes. This is useful for injecting custom headers, logging, monitoring, or transforming responses before the SDK processes them.

Hooks are available in the React SDK (via the AuthProvider component) and the WebJS SDK (via createSdk).

Available Hooks

HookWhen it runsCan modify request?Can modify response?
beforeRequestBefore each outbound requestYesNo
afterRequestAfter each response is receivedNoNo
transformResponseAfter response is received, before SDK processes itNoYes

beforeRequest

Runs before every outbound SDK request. Receives the full request config and must return it — modified or unmodified. Use this to inject headers, append query parameters, or log outbound traffic.

type BeforeRequest = (config: RequestConfig) => RequestConfig;

type RequestConfig = {
  path: string;
  method: string;
  headers?: HeadersInit;
  queryParams?: Record<string, string>;
  body?: any;
  token?: string;
};
import { AuthProvider } from '@descope/react-sdk';

const AppRoot = () => {
  return (
    <AuthProvider
      projectId="__ProjectID__"
      hooks={{
        beforeRequest: (config) => {
          config.headers = {
            ...config.headers,
            'X-Correlation-Id': crypto.randomUUID(),
          };
          return config;
        },
      }}
    >
      <App />
    </AuthProvider>
  );
};
import createSdk from '@descope/web-js-sdk';

const sdk = createSdk({
  projectId: '__ProjectID__',
  hooks: {
    beforeRequest: (config) => {
      config.headers = {
        ...config.headers,
        'X-Correlation-Id': crypto.randomUUID(),
      };
      return config;
    },
  },
});

afterRequest

Runs after a response is received. Receives both the original request config and the Response object. The return value is ignored — use this hook for logging and monitoring only.

type AfterRequest = (req: RequestConfig, res: Response) => void | Promise<void>;
import { AuthProvider } from '@descope/react-sdk';

const AppRoot = () => {
  return (
    <AuthProvider
      projectId="__ProjectID__"
      hooks={{
        afterRequest: async (req, res) => {
          console.log(`[Descope] ${req.method} ${req.path} → ${res.status}`);
        },
      }}
    >
      <App />
    </AuthProvider>
  );
};
import createSdk from '@descope/web-js-sdk';

const sdk = createSdk({
  projectId: '__ProjectID__',
  hooks: {
    afterRequest: async (req, res) => {
      console.log(`[Descope] ${req.method} ${req.path} → ${res.status}`);
    },
  },
});

transformResponse

Runs after the response arrives but before the SDK parses it. The response object is extended with a cookies field containing parsed Set-Cookie values. Return the (optionally modified) response.

type TransformResponse = (res: Response & { cookies: Record<string, string> }) => Promise<Response>;

When to use transformResponse

This hook is primarily useful in environments where Set-Cookie headers are inaccessible to client-side code — for example, when tokens are stored in HttpOnly cookies behind a reverse proxy. It lets you extract cookie values and make them available to the SDK before it processes the response.

import { AuthProvider } from '@descope/react-sdk';

const transformResponse = async (res) => {
  // res.cookies contains parsed Set-Cookie headers
  if (res.cookies.DS) {
    console.log('Session cookie present');
  }
  return res;
};

const AppRoot = () => {
  return (
    <AuthProvider
      projectId="__ProjectID__"
      hooks={{ transformResponse }}
    >
      <App />
    </AuthProvider>
  );
};
import createSdk from '@descope/web-js-sdk';

const sdk = createSdk({
  projectId: '__ProjectID__',
  hooks: {
    transformResponse: async (res) => {
      // res.cookies contains parsed Set-Cookie headers
      if (res.cookies.DS) {
        console.log('Session cookie present');
      }
      return res;
    },
  },
});

Using Multiple Hooks Together

All three hooks can be combined on a single SDK instance:

import { AuthProvider } from '@descope/react-sdk';

const AppRoot = () => {
  return (
    <AuthProvider
      projectId="__ProjectID__"
      hooks={{
        beforeRequest: (config) => {
          config.headers = { ...config.headers, 'X-Request-Start': Date.now().toString() };
          return config;
        },
        afterRequest: async (req, res) => {
          console.log(`[Descope] ${req.method} ${req.path} → ${res.status}`);
        },
        transformResponse: async (res) => res,
      }}
    >
      <App />
    </AuthProvider>
  );
};
import createSdk from '@descope/web-js-sdk';

const sdk = createSdk({
  projectId: '__ProjectID__',
  hooks: {
    beforeRequest: (config) => {
      config.headers = { ...config.headers, 'X-Request-Start': Date.now().toString() };
      return config;
    },
    afterRequest: async (req, res) => {
      console.log(`[Descope] ${req.method} ${req.path} → ${res.status}`);
    },
    transformResponse: async (res) => res,
  },
});

Passing Multiple Functions per Hook

beforeRequest and afterRequest each accept either a single function or an array of functions. When an array is provided, functions run in order — for beforeRequest, each function's output is passed as input to the next.

hooks={{
  beforeRequest: [
    (config) => {
      config.headers = { ...config.headers, 'X-Tenant': 'acme' };
      return config;
    },
    (config) => {
      config.queryParams = { ...config.queryParams, version: '2' };
      return config;
    },
  ],
  afterRequest: [
    (req, res) => console.log('hook 1:', res.status),
    (req, res) => console.log('hook 2:', res.status),
  ],
}}

Error isolation in afterRequest arrays

When afterRequest is an array, each function runs independently. If one function throws, the others still execute — errors are logged but do not propagate.

Was this helpful?

On this page