Webhooks

v1

Let your app know when a post reaches a final state. Verify every event before processing it.

Register an endpoint

Create an HTTPS endpoint from the Webhooks screen or API. Save the signing secret returned on creation; it is only shown once.

TypeScript
const { data: endpoint } = await castrook.webhooks.create({
  url: 'https://your-app.com/webhooks/castrook',
  description: 'Publishing events',
  events: ['post.published', 'post.failed', 'post.partially_failed'],
});

// Store endpoint.secret securely. Do not log it.
EventWhen it fires
post.publishedEvery destination completed
post.partially_failedDelivery ended with mixed results
post.failedEvery destination failed
post.canceledA pending post was canceled

Verify the raw body

Castrook sends webhook-id, webhook-timestamp, and webhook-signature headers. Signatures use HMAC-SHA256 over id.timestamp.raw_body, with v1= followed by a hex digest.

TypeScript
import { verifyWebhook } from '@castrook/sdk';

export async function POST(request: Request) {
  const raw = await request.text();
  const id = request.headers.get('webhook-id') ?? '';
  const valid = await verifyWebhook({
    secret: process.env.CASTROOK_WEBHOOK_SECRET!,
    id,
    timestamp: request.headers.get('webhook-timestamp') ?? '',
    signature: request.headers.get('webhook-signature') ?? '',
    body: raw,
  });

  if (!valid) return new Response('Invalid signature', { status: 401 });
  const event = JSON.parse(raw);

  // Persist event and deduplicate by id before doing work.
  // Queue your application's processing here.
  return new Response(null, { status: 204 });
}

Verify before parsing or modifying the body. Timestamp tolerance defaults to 300 seconds. A valid signature does not replace deduplication.

Delivery and retries

Acknowledge promptly with a 2xx response after safely accepting the event. Delivery is at least once: the same event may arrive more than once. Store processed webhook IDs to make your handler idempotent.

Temporary failures are retried with bounded backoff. Inspect GET /deliveries or the Webhooks screen for attempts, the last response status, and pending or failed deliveries. Disabling an endpoint stops new delivery to it.