Loading...
Loading...
Use when implementing SDK code hooks for custom logic (not spec configuration or runtime overrides). Covers SDK lifecycle hooks: BeforeRequest, AfterSuccess, AfterError for custom headers, telemetry, HMAC signing, and request/response transformation. Triggers on "SDK hooks", "add hooks", "BeforeRequestHook", "custom logic in SDK", "telemetry hook", "SDK middleware", "intercept requests", "HMAC signing hook", "custom code in hooks directory".
npx skill4agent add speakeasy-api/skills customize-sdk-hooksmanage-openapi-overlaysconfigure-sdk-optionssrc/hooks/src/hooks/registration.tssrc/hooks/| Hook | When Called | Common Use Cases |
|---|---|---|
| SDK client initialization | Configure defaults, validate config, set base URL |
| Before the HTTP request object is created | Modify input parameters, inject defaults |
| Before the HTTP request is sent | Add headers, logging, telemetry, sign requests |
| After a successful HTTP response | Transform response, emit warnings, log metrics |
| After an HTTP error response | Error transformation, retry logic, error logging |
// SDKInitHook
interface SDKInitHook {
sdkInit(opts: SDKInitOptions): SDKInitOptions;
}
// BeforeCreateRequestHook
interface BeforeCreateRequestHook {
beforeCreateRequest(hookCtx: BeforeCreateRequestHookContext, input: any): any;
}
// BeforeRequestHook
interface BeforeRequestHook {
beforeRequest(
hookCtx: BeforeRequestHookContext,
request: Request
): Request;
}
// AfterSuccessHook
interface AfterSuccessHook {
afterSuccess(
hookCtx: AfterSuccessHookContext,
response: Response
): Response;
}
// AfterErrorHook
interface AfterErrorHook {
afterError(
hookCtx: AfterErrorHookContext,
response: Response | null,
error: unknown
): { response: Response | null; error: unknown };
}src/
hooks/
types.ts # Generated - DO NOT EDIT (hook interfaces/types)
registration.ts # Custom - YOUR registrations (preserved on regen)
custom_useragent.ts # Custom - your hook implementations
telemetry.ts # Custom - your hook implementationsregistration.tstypes.tssrc/hooks/registration.ts// src/hooks/registration.ts
import { Hooks } from "./types.js";
import { CustomUserAgentHook } from "./custom_useragent.js";
import { TelemetryHook } from "./telemetry.js";
/*
* This file is only ever generated once on the first generation and then is free
* to be modified. Any hooks you wish to add should be registered in the
* initHooks function. Feel free to define them in this file or in separate files
* in the hooks folder.
*/
export function initHooks(hooks: Hooks) {
hooks.registerBeforeRequestHook(new CustomUserAgentHook());
hooks.registerAfterSuccessHook(new TelemetryHook());
}src/hooks/src/hooks/types.tssrc/hooks/registration.ts# After adding hooks, regenerate safely
speakeasy generate sdk -s openapi.yaml -o . -l typescript
# Your registration.ts and custom hook files are untouchedUser-Agent// src/hooks/custom_useragent.ts
import {
BeforeRequestHook,
BeforeRequestHookContext,
} from "./types.js";
export class CustomUserAgentHook implements BeforeRequestHook {
private userAgent: string;
constructor(appName: string, appVersion: string) {
this.userAgent = `${appName}/${appVersion}`;
}
beforeRequest(
hookCtx: BeforeRequestHookContext,
request: Request
): Request {
// Clone the request to add the custom header
const newRequest = new Request(request, {
headers: new Headers(request.headers),
});
newRequest.headers.set("User-Agent", this.userAgent);
// Optionally append the existing User-Agent
const existing = request.headers.get("User-Agent");
if (existing) {
newRequest.headers.set(
"User-Agent",
`${this.userAgent} ${existing}`
);
}
return newRequest;
}
}// src/hooks/registration.ts
import { Hooks } from "./types.js";
import { CustomUserAgentHook } from "./custom_useragent.js";
export function initHooks(hooks: Hooks) {
hooks.registerBeforeRequestHook(
new CustomUserAgentHook("my-app", "1.0.0")
);
}BeforeRequestHook# overlay.yaml
overlay: 1.0.0
info:
title: Add HMAC security
actions:
- target: "$.components.securitySchemes"
update:
hmac_auth:
type: http
scheme: custom
x-speakeasy-custom-security: true// src/hooks/hmac_signing.ts
import {
BeforeRequestHook,
BeforeRequestHookContext,
} from "./types.js";
import { createHmac } from "crypto";
export class HmacSigningHook implements BeforeRequestHook {
beforeRequest(
hookCtx: BeforeRequestHookContext,
request: Request
): Request {
const timestamp = Date.now().toString();
const secret = hookCtx.securitySource?.apiSecret;
if (!secret) {
throw new Error("API secret is required for HMAC signing");
}
const signature = createHmac("sha256", secret)
.update(`${request.method}:${request.url}:${timestamp}`)
.digest("hex");
const newRequest = new Request(request, {
headers: new Headers(request.headers),
});
newRequest.headers.set("X-Timestamp", timestamp);
newRequest.headers.set("X-Signature", signature);
return newRequest;
}
}// src/hooks/registration.ts
import { Hooks } from "./types.js";
import { HmacSigningHook } from "./hmac_signing.js";
export function initHooks(hooks: Hooks) {
hooks.registerBeforeRequestHook(new HmacSigningHook());
}Response.bodyafterSuccess(hookCtx: AfterSuccessHookContext, response: Response): Response {
// CORRECT: clone before reading
const cloned = response.clone();
cloned.json().then((data) => console.log("Response:", data));
return response; // return the original, unconsumed
}beforeRequest(hookCtx: BeforeRequestHookContext, request: Request): Request {
// Fire-and-forget: do not await
void fetch("https://telemetry.example.com/events", {
method: "POST",
body: JSON.stringify({ operation: hookCtx.operationID }),
});
return request;
}RequestResponsehookCtx.operationIDtypes.tsAfterSuccessHooksrc/hooks/registration.tsregisterBeforeRequestHookregisterAfterSuccessHookinitHooksresponse.bodyresponse.json()response.clone()src/hooks/registration.tstypes.tstypes.tsRequestResponsehooks/hooks.gohooks/registration.gosrc/hooks/src/hooks/types.pyhooks/SDKHooks.javaHooks/SDKHooks.cstypes