TypeScript SDK

Webhooks

Subscribe to prediction lifecycle events and receive verified HMAC-SHA256 callbacks on your server.

The SDK provides type-safe webhook helpers for subscribing to prediction events and verifying inbound webhook notifications on your server.

SidePurposeSDK feature
OutboundAttach a callback URL to a prediction requestWebhook class, WebhookEvent enum
InboundReceive and verify Skytells's HTTP POSTs on your serverWebhookListener, verifySkytellsWebhookSignature

Outbound — Subscribing to Events

Webhook class

Attach a webhook when creating a prediction. You can use the Webhook class or a plain object.

The SDK provides a WebhookEvent enum with type-safe event constants: COMPLETED, FAILED, CANCELED, STARTED.

Outbound Webhooks

Webhook class
import Skytells, { Webhook, WebhookEvent } from 'skytells';

const client = Skytells(process.env.SKYTELLS_API_KEY);

await client.predictions.create({
model: 'flux-pro',
input: { prompt: 'An astronaut on Mars' },
webhook: new Webhook(
  'https://api.example.com/hooks/skytells',
  [WebhookEvent.COMPLETED, WebhookEvent.FAILED],
).toJSON(),
});

Inbound — WebhookListener

WebhookListener handles the full inbound pipeline: signature verification → JSON parse → handler dispatch.

Create one with createWebhookListener() or the client factory client.webhookListener().

WebhookListener Setup

Standalone
import { createWebhookListener, WebhookEvent } from 'skytells';

const listener = createWebhookListener({
mode: 'general',
apiKey: process.env.SKYTELLS_API_KEY!,
verifySignature: true, // default, always true in prod
});

listener.on(WebhookEvent.COMPLETED, async (prediction) => {
console.log('Completed:', prediction.id);
console.log('Output:', prediction.output);
await saveToDatabase(prediction);
});

listener.on(WebhookEvent.FAILED, async (prediction) => {
console.error('Failed:', prediction.id, prediction.response);
});

Framework Integration

Use handleRequest(req) for frameworks that expose a Web Request object (Next.js, Hono, Cloudflare Workers). For Express, use handle(rawBody, headers) with raw body middleware.

Framework Examples

Next.js
// app/api/webhooks/skytells/route.ts
import { createWebhookListener, WebhookEvent } from 'skytells';

const listener = createWebhookListener({
mode: 'general',
apiKey: process.env.SKYTELLS_API_KEY!,
});

listener.on(WebhookEvent.COMPLETED, async (prediction) => {
// handle completed prediction
});

export async function POST(req: Request): Promise<Response> {
return listener.handleRequest(req);
}

Route Matching

listener.on() (alias: listen()) accepts these route patterns:

PatternFires on
WebhookEvent.COMPLETED / 'completed'status === 'succeeded'
WebhookEvent.FAILED / 'failed'status === 'failed'
WebhookEvent.CANCELED / 'canceled'status === 'cancelled'
WebhookEvent.STARTED / 'started'status === 'started' or 'starting'
'prediction.succeeded'Exact status match
'prediction.failed'Exact status match
'prediction.cancelled'Exact status match
'prediction.pending'Exact status match
'prediction.*'Any prediction status change
'*'All events

Multiple handlers on the same route run sequentially in registration order:

listener.on('*', async (p) => { await logToAuditLog(p); });
listener.on(WebhookEvent.COMPLETED, async (p) => { await processOutput(p); });
listener.on(WebhookEvent.FAILED, async (p) => { await alertOps(p); });

// Remove a handler
listener.off(WebhookEvent.COMPLETED, handler);

handle() and dispatch()

handle(rawBody, headers)Promise<PredictionResponse>

Verify signature, parse JSON, run handlers. Returns PredictionResponse. Throws WEBHOOK_SIGNATURE_INVALID or INVALID_JSON.

dispatch(prediction)Promise<void>

Skip signature verification. Only use in trusted, pre-verified contexts.

handleRequest(req)Promise<Response>

Web RequestResponse. Returns 200 on success, 401 on invalid signature, 400 on invalid JSON.

handleRequest() Response Codes

ConditionStatusBody
Handlers completed200{ ok: true }
Invalid signature401{ error, errorId: 'WEBHOOK_SIGNATURE_INVALID' }
Invalid JSON body400{ error, errorId: 'INVALID_JSON' }
Other SkytellsErrorhttpStatus{ error, errorId }

Manual Signature Verification

The SDK provides verifySkytellsWebhookSignature for manual signature verification if you're not using WebhookListener:

import { verifySkytellsWebhookSignature } from 'skytells';

const isValid = await verifySkytellsWebhookSignature(
  rawBodyString,            // exact bytes as received
  req.headers['x-skytells-signature'],
  { mode: 'general', apiKey: process.env.SKYTELLS_API_KEY! },
);

if (!isValid) {
  return res.status(401).json({ error: 'Invalid signature' });
}

const prediction = JSON.parse(rawBodyString);

Verification modes

ModeHMAC key
'general'Your sk-… API key
'enterprise'Dashboard webhook secret

Security Best Practices

SDK-specific notes:

  • Always verify signatures in productionverifySignature: false is only for local development.
  • Read the raw body before any middleware parses it — body parsers consume the stream.
  • Never re-serialize: JSON.stringify(JSON.parse(rawBody)) breaks HMAC because key order and whitespace may differ.
  • The SDK uses Web Crypto (crypto.subtle) for HMAC-SHA256, requiring:
    • Node.js 19+ (built-in crypto.subtle)
    • Node.js 18 with --experimental-global-webcrypto
    • Edge runtimes (Cloudflare Workers, Vercel Edge) — natively supported
  • The hex comparison is timing-safe to prevent timing attacks.
  • Webhooks API — Webhook concepts, event types, payload structure, and general setup
  • Predictions — Attach webhooks when creating predictions
  • Models — Discover available models for prediction webhooks
  • Errors — All error IDs including WEBHOOK_SIGNATURE_INVALID
  • Reliability — Timeouts, retries, and polling alternatives

How is this guide?

On this page