Install
$ agentstack add skill-speakeasy-api-skills-customize-sdk-hooks ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Customize SDK Hooks
When to Use
Use this skill when you need to add custom code logic to the generated SDK:
- Add custom headers (User-Agent, correlation IDs) to every SDK request via code
- Implement telemetry, logging, or observability at the SDK level
- Add custom authentication logic (HMAC signatures, token refresh) that runs in SDK code
- Transform responses or errors before they reach the caller
- Implement custom request/response middleware
- User says: "SDK hooks", "add custom logic", "intercept requests with code", "HMAC signing hook", "telemetry in SDK"
NOT for:
- OpenAPI spec modifications (see
manage-openapi-overlays) - Runtime SDK client config (see
configure-sdk-options)
Inputs
- Hook type: Which lifecycle event to intercept (init, before request, after success, after error)
- SDK language: The target language of the generated SDK (TypeScript, Go, Python, Java, C#, Ruby, PHP, etc.)
- Custom logic: The behavior to inject at the hook point
Outputs
- Hook implementation file(s) in
src/hooks/(or language equivalent) - Updated
src/hooks/registration.tsto register the new hook - The hook is preserved across SDK regenerations
Prerequisites
- A Speakeasy-generated SDK (any supported language)
- Understanding of the SDK's request/response lifecycle
- The
src/hooks/directory exists in the generated SDK (created by default)
Hook Types
| 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 |
Hook Interfaces (TypeScript)
// 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 };
}
Directory Structure
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.
Registration Pattern
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());
}
Command
To add a hook to a Speakeasy-generated SDK:
- Create your hook implementation file in
src/hooks/ - Implement the appropriate interface from
src/hooks/types.ts - Register it in
src/hooks/registration.ts - Regenerate the SDK -- your hooks are preserved
# After adding hooks, regenerate safely
speakeasy generate sdk -s openapi.yaml -o . -l typescript
# Your registration.ts and custom hook files are untouched
Examples
Example 1: Custom User-Agent Hook
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")
);
}
Example 2: Custom Security Hook (HMAC Signing)
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());
}
Best Practices
- 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.bodystream can only
be consumed once. Always clone before reading:
``typescript 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:
``typescript 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.
What NOT to Do
- Do NOT edit
types.ts: This file is regenerated. Your changes will be lost. - Do NOT consume the response body without cloning: This causes downstream
failures because the body stream is exhausted.
- Do NOT perform blocking I/O in hooks: Long-running operations (network calls,
file I/O) in hooks will degrade SDK performance. Use fire-and-forget patterns for non-critical work.
- Do NOT throw errors in
AfterSuccessHookunless you intend to convert a
success into a failure. Throwing in hooks disrupts the normal flow.
- Do NOT store mutable shared state in hooks without synchronization. Hooks may
be called concurrently in multi-threaded environments.
- Do NOT duplicate logic that belongs in an OpenAPI overlay. If you need to
modify the API spec (add security schemes, change parameters), use an overlay instead of a hook.
Troubleshooting
Hook is not being called
- Verify the hook is registered in
src/hooks/registration.ts - Confirm you are registering for the correct hook type (e.g.,
registerBeforeRequestHook
vs registerAfterSuccessHook)
- Check that
initHooksis exported and follows the expected signature
Response body is empty or already consumed
- You are reading
response.bodyor callingresponse.json()without cloning first - Always use
response.clone()before consuming the body, then return the original
Hooks lost after regeneration
- Custom hook files in
src/hooks/are preserved, but only if they are separate files registration.tsis never overwritten after initial generationtypes.tsIS overwritten -- never put custom code there
TypeScript compilation errors after regeneration
types.tsmay have updated interfaces. Check for breaking changes in hook signatures- Update your hook implementations to match the new interface definitions
Hook causes request failures
- Ensure you are returning a valid
RequestorResponseobject - Check that cloned requests preserve the original body and headers
- Verify any injected headers have valid values (no undefined or null)
Other Languages
While the examples above are in TypeScript, Speakeasy SDK hooks are available across all supported languages:
- Go: Hooks implement interfaces in
hooks/hooks.gowith registration in
hooks/registration.go
- Python: Hooks are classes in
src/hooks/implementing protocols from
src/hooks/types.py
- Java: Hooks implement interfaces from
hooks/SDKHooks.java - C#: Hooks implement interfaces from
Hooks/SDKHooks.cs - Ruby: Hooks use Sorbet-typed classes with Faraday middleware patterns
- PHP: Hooks use PSR-7 request/response interfaces with Guzzle middleware
The 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.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: speakeasy-api
- Source: speakeasy-api/skills
- License: Apache-2.0
- Homepage: https://www.speakeasy.com/docs
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.