# Edge runtimes

> Run @hatched/sdk-js on Cloudflare Workers, Vercel Edge, Deno, and Bun — fetch overrides, AbortSignal, and the crypto caveat.

Source: https://docs.hatched.live/docs/guides/edge-runtimes

The SDK is written against native web standards — `fetch`, `Response`,
`AbortSignal`, `crypto.randomUUID` — so it runs unmodified on every modern
edge runtime.

## Cloudflare Workers

```ts
import { HatchedClient } from '@hatched/sdk-js';

export interface Env {
  HATCHED_API_KEY: string;
}

export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    const hatched = new HatchedClient({ apiKey: env.HATCHED_API_KEY });
    const health = await hatched.health();
    return Response.json(health);
  },
};
```

The server-only guard passes because Workers don't have a `window` or
`document` global.

### Webhooks on Workers

`WebhooksResource.verifySignature` uses `node:crypto`, which **does not**
run on Workers. For Workers, verify manually with Web Crypto:

```ts
async function verify(body: string, header: string, secret: string): Promise<boolean> {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const ts = parts.t;
  const sig = parts.v1;
  if (!ts || !sig) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  const expected = await crypto.subtle.sign(
    'HMAC',
    key,
    new TextEncoder().encode(`${ts}.${body}`),
  );
  const expectedHex = Array.from(new Uint8Array(expected))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
  return timingSafeEqualHex(expectedHex, sig);
}

function timingSafeEqualHex(a: string, b: string): boolean {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return diff === 0;
}
```

## Vercel Edge

```ts
// app/api/hatched/health/route.ts
export const runtime = 'edge';

import { HatchedClient } from '@hatched/sdk-js';

export async function GET() {
  const hatched = new HatchedClient({ apiKey: process.env.HATCHED_API_KEY! });
  return Response.json(await hatched.health());
}
```

For webhook verification with `node:crypto`, switch to
`runtime = 'nodejs'`. Everything else (reads, event sends, widget session
mint) works on Edge.

## Deno

```ts
import { HatchedClient } from 'npm:@hatched/sdk-js';

const hatched = new HatchedClient({ apiKey: Deno.env.get('HATCHED_API_KEY')! });
console.log(await hatched.health());
```

## Bun

```ts
import { HatchedClient } from '@hatched/sdk-js';
const hatched = new HatchedClient({ apiKey: Bun.env.HATCHED_API_KEY! });
```

## Custom fetch override

Pass your own `fetch` — useful for:

- Preflighting every request through a telemetry hop
- Forcing a specific outbound pool on Workers (`fetch(input, { cf: {...} })`)
- Injecting retries via a shared HTTP client

```ts
const hatched = new HatchedClient({
  apiKey: env.HATCHED_API_KEY,
  fetch: async (input, init) => {
    const start = Date.now();
    const res = await fetch(input, init);
    metrics.record('hatched.http', Date.now() - start);
    return res;
  },
});
```

## Cancellation

`AbortSignal.any` is used internally to combine your signal with the SDK
timeout. If your runtime doesn't have `AbortSignal.any` (very old
environments), the SDK falls back to a manual combinator — no action
needed on your side.
