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
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 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
- Drift-specific subscription: Handle voice drift tutorial.
- Signature verification deep-dive: Webhook signature verification guide.
- Full event list: Webhook events reference.
- Webhooks API reference.