From speakeasy
Implements SDK lifecycle hooks (BeforeRequest, AfterSuccess, AfterError) for custom headers, telemetry, HMAC signing, and request/response transformations in generated SDKs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/speakeasy:customize-sdk-hooksThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when you need to **add custom code logic** to the generated SDK:
Use this skill when you need to add custom code logic to the generated SDK:
NOT for:
manage-openapi-overlays)configure-sdk-options)src/hooks/ (or language equivalent)src/hooks/registration.ts to register the new hooksrc/hooks/ directory exists in the generated SDK (created by default)| Hook | When Called | Common Use Cases |
|---|---|---|
SDKInitHook | SDK client initialization | Configure defaults, validate config, set base URL |
BeforeCreateRequestHook | Before the HTTP request object is created | Modify input parameters, inject defaults |
BeforeRequestHook | Before the HTTP request is sent | Add headers, logging, telemetry, sign requests |
AfterSuccessHook | After a successful HTTP response | Transform response, emit warnings, log metrics |
AfterErrorHook | 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 implementations
Key rule: registration.ts and any custom hook files you create are preserved
during SDK regeneration. The types.ts file is regenerated and should not be modified.
All hooks are registered in src/hooks/registration.ts. This file is created once
by the generator and never overwritten. You add your hooks here:
// 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());
}
To add a hook to a Speakeasy-generated SDK:
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 untouched
Add a custom User-Agent header to every outgoing request.
// 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;
}
}
Register it:
// 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")
);
}
For APIs requiring HMAC signatures or custom authentication that cannot be
expressed in the OpenAPI spec, combine an overlay with a BeforeRequestHook.
Step 1: Use an overlay to mark the security scheme so Speakeasy generates the hook point:
# 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
Step 2: Implement the signing hook:
// 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;
}
}
Register it:
// src/hooks/registration.ts
import { Hooks } from "./types.js";
import { HmacSigningHook } from "./hmac_signing.js";
export function initHooks(hooks: Hooks) {
hooks.registerBeforeRequestHook(new HmacSigningHook());
}
Keep hooks focused: Each hook should address a single concern. Use separate hooks for user-agent, telemetry, and auth rather than one monolithic hook.
Clone responses before reading the body: The Response.body stream can only
be consumed once. Always clone before reading:
afterSuccess(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
}
Fire-and-forget for telemetry: Do not block the request pipeline for non-critical operations like logging or metrics:
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;
}
Test hooks independently: Write unit tests for hooks in isolation by
constructing mock Request/Response objects and hook contexts.
Use hookCtx.operationID: The hook context provides the current operation
ID, which is useful for per-operation behavior, logging, and metrics.
types.ts: This file is regenerated. Your changes will be lost.AfterSuccessHook unless you intend to convert a
success into a failure. Throwing in hooks disrupts the normal flow.src/hooks/registration.tsregisterBeforeRequestHook
vs registerAfterSuccessHook)initHooks is exported and follows the expected signatureresponse.body or calling response.json() without cloning firstresponse.clone() before consuming the body, then return the originalsrc/hooks/ are preserved, but only if they are separate filesregistration.ts is never overwritten after initial generationtypes.ts IS overwritten -- never put custom code theretypes.ts may have updated interfaces. Check for breaking changes in hook signaturesRequest or Response objectWhile the examples above are in TypeScript, Speakeasy SDK hooks are available across all supported languages:
hooks/hooks.go with registration in
hooks/registration.gosrc/hooks/ implementing protocols from
src/hooks/types.pyhooks/SDKHooks.javaHooks/SDKHooks.csThe hook types, lifecycle, and registration pattern are consistent across all
languages. Refer to the generated types file in your SDK for language-specific
interface definitions.
npx claudepluginhub speakeasy-api/skills --plugin speakeasy2plugins reuse this skill
First indexed Jul 8, 2026
Configures gen.yaml options and runtime overrides (retries, timeouts, server selection) for existing Speakeasy SDKs in TypeScript, Python, Go, Java, C#, PHP, Ruby.
Generates SDK wrappers for third-party APIs, including code, configs, and integration patterns.
Generates type-safe client SDKs in TypeScript, Python, Go, Java from OpenAPI specs with auth, retries, pagination, and tests.