# Customize Sdk Hooks

> |

- **Type:** Skill
- **Install:** `agentstack add skill-speakeasy-api-skills-customize-sdk-hooks`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [speakeasy-api](https://agentstack.voostack.com/s/speakeasy-api)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [speakeasy-api](https://github.com/speakeasy-api)
- **Source:** https://github.com/speakeasy-api/skills/tree/master/skills/customize-sdk-hooks
- **Website:** https://www.speakeasy.com/docs

## Install

```sh
agentstack add skill-speakeasy-api-skills-customize-sdk-hooks
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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.ts` to 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)

```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:

```typescript
// 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:

1. Create your hook implementation file in `src/hooks/`
2. Implement the appropriate interface from `src/hooks/types.ts`
3. Register it in `src/hooks/registration.ts`
4. Regenerate the SDK -- your hooks are preserved

```bash
# 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.

```typescript
// 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:

```typescript
// 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:

```yaml
# 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:

```typescript
// 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:

```typescript
// 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

1. **Keep hooks focused**: Each hook should address a single concern. Use separate
   hooks for user-agent, telemetry, and auth rather than one monolithic hook.

2. **Clone responses before reading the body**: The `Response.body` stream 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
   }
   ```

3. **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;
   }
   ```

4. **Test hooks independently**: Write unit tests for hooks in isolation by
   constructing mock `Request`/`Response` objects and hook contexts.

5. **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 `AfterSuccessHook`** unless 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 `initHooks` is exported and follows the expected signature

### Response body is empty or already consumed

- You are reading `response.body` or calling `response.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.ts` is never overwritten after initial generation
- `types.ts` IS overwritten -- never put custom code there

### TypeScript compilation errors after regeneration

- `types.ts` may 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 `Request` or `Response` object
- 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.go` with 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](https://github.com/speakeasy-api)
- **Source:** [speakeasy-api/skills](https://github.com/speakeasy-api/skills)
- **License:** Apache-2.0
- **Homepage:** https://www.speakeasy.com/docs

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-speakeasy-api-skills-customize-sdk-hooks
- Seller: https://agentstack.voostack.com/s/speakeasy-api
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
