Skip to main content
Webhooks are the recommended way to receive results. Register one or more HTTPS endpoints; Selektable POSTs a signed event when a generation reaches a terminal state.

Events

EventFires when
generation.completedA generation finishes successfully.
generation.failedA generation fails (including after all retries).
Subscribe to specific events by name or use ["*"] to receive all.

Register an endpoint

curl -X POST https://api.selektable.com/v1/webhook-endpoints \
  -H "Authorization: Bearer selektable_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/hooks/selektable",
    "enabledEvents": ["*"]
  }'
Returns 201 including the signing secret — shown once, store it securely:
{
  "id": "whe_xyz789",
  "object": "webhook_endpoint",
  "url": "https://yourapp.com/hooks/selektable",
  "description": null,
  "enabledEvents": ["*"],
  "status": "enabled",
  "createdAt": "2026-06-10T12:00:00.000Z",
  "updatedAt": "2026-06-10T12:00:00.000Z",
  "secret": "whsec_Base64SigningSecret..."
}
Manage endpoints with the webhook endpoint routes.

Event payload

{
  "id": "evt_9f8e7d",
  "type": "generation.completed",
  "createdAt": "2026-06-10T12:00:05.000Z",
  "data": {
    "id": "agen_a1b2c3d4",
    "object": "generation",
    "status": "complete",
    "imageUrl": "https://cdn.selektable.com/.../agen_a1b2c3d4.png",
    "imageVariants": {
      "list":   { "url": "https://cdn.selektable.com/cdn-cgi/image/width=200,quality=70,format=auto,fit=scale-down/.../agen_a1b2c3d4.png",  "width": 200  },
      "card":   { "url": "https://cdn.selektable.com/cdn-cgi/image/width=600,quality=80,format=auto,fit=scale-down/.../agen_a1b2c3d4.png",  "width": 600  },
      "detail": { "url": "https://cdn.selektable.com/cdn-cgi/image/width=1200,quality=85,format=auto,fit=scale-down/.../agen_a1b2c3d4.png", "width": 1200 }
    },
    "...": "...full generation object..."
  }
}
data is the same shape as the Generation object.

Verifying signatures

Selektable signs every webhook using the Standard Webhooks scheme. Each request includes:
HeaderDescription
webhook-idUnique message id (= event.id).
webhook-timestampUnix seconds when sent.
webhook-signatureSpace-delimited list of v1,<signature> values.
The signature is computed as:
base64( HMAC_SHA256( secret, "{webhook-id}.{webhook-timestamp}.{rawBody}" ) )
where secret is the base64-decoded portion of your whsec_ key (everything after the whsec_ prefix).

Node.js example

import crypto from 'node:crypto';

function verifySelektableWebhook(req, secret) {
  const id = req.headers['webhook-id'];
  const timestamp = req.headers['webhook-timestamp'];
  const signatureHeader = req.headers['webhook-signature'];
  const rawBody = req.rawBody; // the exact bytes received

  // Reject stale messages (guards against replay)
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (age > 300) throw new Error('Timestamp too old');

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const signedContent = `${id}.${timestamp}.${rawBody}`;
  const expected =
    'v1,' + crypto.createHmac('sha256', key).update(signedContent).digest('base64');

  const ok = signatureHeader
    .split(' ')
    .some(
      (candidate) =>
        candidate.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(candidate), Buffer.from(expected))
    );

  if (!ok) throw new Error('Invalid signature');
}
Always verify against the raw request body before parsing JSON. Re-serializing parsed JSON changes whitespace and breaks signature verification.

Delivery & retries

  • Endpoints should return a 2xx status quickly (within ~10 seconds).
  • Non-2xx responses are retried with exponential backoff (up to 5 attempts).
  • Make your handler idempotent by deduplicating on event.id — under retry, the same event may arrive more than once.

Polling alternative

If you can’t accept webhooks, poll GET /v1/generations/{id} until status is complete or failed. Recommended cadence: every 1–2 seconds, with a sensible overall timeout (most renders finish within ~30s). Webhooks and polling can be used together.