Open app
Moonborn — Developers

Webhooks

HMAC-SHA256 signed event deliveries, five retries with exponential backoff, replayable from the dashboard or API. Sixteen real event types.

Webhooks are how Moonborn pushes runtime events into your stack — persona lifecycle, generation pipeline outcomes, billing changes, moderation flags. Sixteen real event types ship today (see Webhook events reference).

Subscribe

const hook = await client.webhooks.createWebhook({
  url: 'https://your-app.com/webhooks/moonborn',
  events: ['persona.created', 'persona.audit_failed'],
  description: 'Production listener',
});
 
console.log(hook.signingSecret);
// ⚠ Returned exactly once. Store it; it's never readable again.

Use events: ['*'] to subscribe to all events. Per-event lists keep your handlers tighter.

Verify

Every delivery carries an X-Moonborn-Signature header:

X-Moonborn-Signature: t=1747497600,v1=2c4f8...

The t is the Unix timestamp; v1 is the HMAC-SHA256 of {t}.{rawBody} keyed by the signing secret.

import crypto from 'node:crypto';
 
function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.split('=')),
  );
  const t = parts['t'];
  const v1 = parts['v1'];
  const signed = `${t}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signed)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(v1 ?? '', 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

Reject deliveries older than 5 minutes (t timestamp) to prevent replay attacks. See the Webhook signature verification guide for production-grade variants.

Retry policy

AttemptDelay
1immediate
21 minute
35 minutes
430 minutes
52 hours

After five failures, the delivery moves to the dead-letter queue. The endpoint stays active; subsequent events still attempt delivery.

Configurable in api.webhooks.retry.* (org scope, Enterprise can tune the schedule).

Replay

Failed deliveries are replayable:

const failed = await client.webhooks.listDeliveries({
  webhookId: hook.id,
  status: 'failed',
});
 
for (const d of failed) {
  await client.webhooks.replayDelivery({
    webhookId: hook.id,
    deliveryId: d.id,
  });
}

Replays preserve the original payload + signature timestamp. Your handler must treat them as idempotent; use the id field of the event payload as your dedupe key.

Test deliveries

POST /v1/webhooks/{id}/ping fires a synthetic webhook.endpoint.test_ping event so you can verify the receiver without waiting for a real event.

Rotate the secret

await client.webhooks.rotateSecret({ id: hook.id });

Returns a new signing secret. The old one stays valid for the configured grace period (api.webhooks.secret_rotation_grace_minutes, default 60). Update your receiver, then let the old secret expire.

Tier

Team and up.

Honest scope

Webhooks deliver events. They are not a streaming API for real-time chat or generation progress — that's SSE on the chat / personas endpoints. Use webhooks for edge-triggered notifications (something happened, take action), not for tailing a stream.

Next