HatchedDocs
Guides

Edge runtimes

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

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

Cloudflare Workers

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:

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

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

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

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

Bun

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
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.