> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.ekanos.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# On-activate

> Hook that runs after activation for cache seeding and eager validation — not a connect gate.

`onActivate` is an optional `(ctx) => Promise<void>` hook that runs server-side after an activation is first persisted, and again whenever the activation data is updated. Use it to seed caches and sanity-check configuration early.

## Basic hook

```ts theme={null}
import type { OnActivateHandler } from '@ekanos/sdk/integration';
import { ACME_API_KEY } from './config';

export const seedOnConnect: OnActivateHandler<MyStorage> = async (ctx) => {
  const apiKey = await ctx.secrets.get(ACME_API_KEY);
  if (!apiKey) {
    return; // Not configured yet — not an error
  }

  const response = await ctx.fetch('https://api.acme.example/v1/summary', {
    headers: { authorization: `Bearer ${apiKey}` },
  });

  if (!response.ok) {
    throw new Error(`Acme returned ${response.status} while seeding cache.`);
  }

  const summary = await response.json();
  await ctx.storage.account.set('cache/summary', summary);
};

// In defineIntegration:
export const integration = defineIntegration({
  onActivate: seedOnConnect,
});
```

## When it runs

1. Once after activation is first persisted
2. Again after the user updates their activation data (e.g., re-enters the API key)

Same `ctx`, same enforcement as schedules and tools.

## Error handling

**v1 errors are non-fatal.** A throw is logged and shown to the user as a warning — the activation stays connected. This hook is for cache seeding and eager validation, not a connect gate: it cannot reject a connection. Do not repurpose it as a credential validator that must fail the connect.

```ts theme={null}
async (ctx) => {
  const apiKey = await ctx.secrets.get('api_key');
  if (!apiKey) {
    return; // Expected — user hasn't set it yet
  }

  const response = await ctx.fetch('https://api.example/validate');
  if (!response.ok) {
    throw new Error(`Validation failed: ${response.status}`);
    // Logged as warning; user sees it but activation succeeds
  }
};
```

## Cache invalidation with fingerprints

When cache depends on activation data, store a fingerprint. On read, treat a mismatch as a cache miss:

```ts theme={null}
// In schema:
const CacheSchema = z.object({
  forecast: z.unknown(),
  configFingerprint: z.string().optional(),
});

// On activate (`hash` is your own helper):
const settings = await ctx.storage.account.get('settings');
const fingerprint = hash(JSON.stringify(settings?.data ?? null));

await ctx.storage.account.set('cache/forecast', {
  forecast: await fetchForecast(),
  configFingerprint: fingerprint,
});

// On read in a schedule:
const cached = await ctx.storage.account.get('cache/forecast');
const currentFingerprint = hash(JSON.stringify(await getCurrentConfig()));

if (!cached || cached.data.configFingerprint !== currentFingerprint) {
  // Cache is stale or missing
}
```

Fingerprint every activation field that changes the cached **values**, not only the ones that select which data to fetch (units, currency, and locale leave a cache key untouched while making every number wrong). Make new fingerprint fields `.optional()`: a required field throws `StorageValidationError` on every pre-existing row, which for a `clientReadable` key reaches the widget.

## Testing

Use `invokeActivate()`:

```ts theme={null}
import { invokeActivate } from '@ekanos/sdk/testing';

const { ctx } = await invokeActivate(integration, {
  contextOptions: {
    secrets: { account: { acme_api_key: 'test_key' } },
    fetchHandlers: [
      { match: 'https://api.acme.example', respond: () => Response.json({}) },
    ],
  },
});

expect(ctx.dumpStorage().account['cache/summary']).toBeDefined();
```

`invokeActivate()` throws if `onActivate` is not declared, and lets a handler's throw propagate — it is a bare invoker. The non-fatal handling is applied by whatever calls the hook (the host, or the harness's activation surface), so a test of that behavior should catch the rejection itself.

## Next steps

* [Capability context](/sdk/capability-context) — use ctx in hooks
* [Testing](/sdk/testing) — test with invokeActivate()


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.