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.
For webhook concepts, event types, payload structure, and general setup instructions, see the Webhooks API documentation. This page covers SDK-specific features only.
| Side | Purpose | SDK feature |
|---|---|---|
| Outbound | Attach a callback URL to a prediction request | Webhook class, WebhookEvent enum |
| Inbound | Receive and verify Skytells's HTTP POSTs on your server | WebhookListener, verifySkytellsWebhookSignature |
Outbound — Subscribing to Events
Webhook class
Attach a webhook when creating a prediction. You can use the Webhook class or a plain object.
For available webhook event types and when they fire, see Webhook Events in the API documentation.
The SDK provides a WebhookEvent enum with type-safe event constants: COMPLETED, FAILED, CANCELED, STARTED.
Outbound Webhooks
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
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.
Read the raw body before any middleware parses it. Body parsers (JSON, form) consume the stream. Pass the raw bytes/string to the listener.
Framework Examples
// 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:
| Pattern | Fires 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 Request → Response. Returns 200 on success, 401 on invalid signature, 400 on invalid JSON.
handleRequest() Response Codes
| Condition | Status | Body |
|---|---|---|
| Handlers completed | 200 | { ok: true } |
| Invalid signature | 401 | { error, errorId: 'WEBHOOK_SIGNATURE_INVALID' } |
| Invalid JSON body | 400 | { error, errorId: 'INVALID_JSON' } |
Other SkytellsError | httpStatus | { error, errorId } |
Manual Signature Verification
For webhook security principles and HMAC-SHA256 signature verification concepts, see Webhook Security in the API documentation.
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
| Mode | HMAC key |
|---|---|
'general' | Your sk-… API key |
'enterprise' | Dashboard webhook secret |
Security Best Practices
For general webhook security best practices, see Webhook Security and Handling Webhooks in the API documentation.
SDK-specific notes:
- Always verify signatures in production —
verifySignature: falseis 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
- Node.js 19+ (built-in
- The hex comparison is timing-safe to prevent timing attacks.
Related
- 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?