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
| Hook | When it runs | Can modify request? | Can modify response? |
|---|---|---|---|
beforeRequest | Before each outbound request | Yes | No |
afterRequest | After each response is received | No | No |
transformResponse | After response is received, before SDK processes it | No | Yes |
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.