Payload structure
Every webhook delivery shares the same envelope format:
{
"webhook_event": "generation.completed",
"webhook_timestamp": "2025-01-15T12:00:00.000Z",
"webhook_delivery_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"webhook_data": { ... }
}
Event type. One of generation.started, generation.completed, generation.failed, generation.canceled, credits.low_balance, or webhook.test.
ISO 8601 timestamp when the event was dispatched.
Unique delivery identifier (UUID). Use this as an idempotency key to handle duplicate deliveries.
Event payload. Generation events use generation fields, while credits.low_balance uses balance-alert fields.
webhook_data fields for generation events
Only fields with values are included. Optional fields are omitted (not set to null) when they do not apply to the event.
| Field | Type | Present on |
|---|
account_id | string | All events |
model_identifier | string | All events |
generation_id | string | All events |
generation_status | string | All events |
generation_provider_initialize | string | generation.started |
generation_provider_used | string | generation.completed |
generation_prediction_id | string | generation.started, generation.completed, generation.canceled |
generation_output_file | string[] | generation.completed |
generation_error | string | generation.failed |
generation_error_code | string | generation.failed |
credits_refunded | boolean | generation.canceled |
credits.low_balance
This event is sent when the account balance crosses one or more configured alert thresholds.
{
"webhook_event": "credits.low_balance",
"webhook_timestamp": "2025-01-15T12:00:00.000Z",
"webhook_delivery_id": "f6a7b8c9-d0e1-2345-f012-456789012345",
"webhook_data": {
"account_id": "your-account-id",
"current_balance": 8.5,
"thresholds_crossed": [
{
"threshold": 10,
"balance_at": 8.5
}
]
}
}
| Field | Value |
|---|
current_balance | The account balance at the time the alert was generated |
thresholds_crossed | Array of threshold records that triggered the alert |
thresholds_crossed[].threshold | The configured threshold value that was crossed |
thresholds_crossed[].balance_at | The balance recorded when BabySea generated the alert |
generation.started
This event uses generation_status: processing.
{
"webhook_event": "generation.started",
"webhook_timestamp": "2025-01-15T12:00:00.000Z",
"webhook_delivery_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"webhook_data": {
"account_id": "your-account-id",
"model_identifier": "your-model-identifier",
"generation_provider_initialize": "provider-name",
"generation_status": "processing",
"generation_prediction_id": "abc123",
"generation_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
| Field | Value |
|---|
generation_status | processing |
generation_provider_initialize | The provider that received the generation request |
generation_prediction_id | The provider’s prediction identifier |
generation.completed
This event uses generation_status: succeeded.
{
"webhook_event": "generation.completed",
"webhook_timestamp": "2025-01-15T12:00:15.000Z",
"webhook_delivery_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"webhook_data": {
"account_id": "your-account-id",
"model_identifier": "your-model-identifier",
"generation_provider_used": "provider-name",
"generation_status": "succeeded",
"generation_prediction_id": "abc123",
"generation_id": "550e8400-e29b-41d4-a716-446655440000",
"generation_output_file": [
"https://your-storage-host.example/output-0.png"
]
}
}
| Field | Value |
|---|
generation_status | succeeded |
generation_provider_used | The provider that produced the final output |
generation_output_file | Array of URLs pointing to the generated files |
generation.failed
This event uses generation_status: failed.
{
"webhook_event": "generation.failed",
"webhook_timestamp": "2025-01-15T12:00:30.000Z",
"webhook_delivery_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"webhook_data": {
"account_id": "your-account-id",
"model_identifier": "your-model-identifier",
"generation_status": "failed",
"generation_id": "550e8400-e29b-41d4-a716-446655440000",
"generation_error": "Generation request failed",
"generation_error_code": "BSE4001"
}
}
| Field | Value |
|---|
generation_status | failed |
generation_error | Human-readable error description |
generation_error_code | BabySea error code. See SDK Errors for the full list |
generation.canceled
This event uses generation_status: canceled.
{
"webhook_event": "generation.canceled",
"webhook_timestamp": "2025-01-15T12:00:10.000Z",
"webhook_delivery_id": "d4e5f6a7-b8c9-0123-def0-234567890123",
"webhook_data": {
"account_id": "your-account-id",
"model_identifier": "your-model-identifier",
"generation_status": "canceled",
"generation_prediction_id": "abc123",
"generation_id": "550e8400-e29b-41d4-a716-446655440000",
"credits_refunded": true
}
}
| Field | Value |
|---|
generation_status | canceled |
generation_prediction_id | Included if the generation was already submitted to a provider before cancellation |
credits_refunded | true if credits were refunded, false otherwise |
webhook.test
This manual event is sent when you click Send test event on Webhook details. It uses a fixed test generation ID and a single delivery attempt.
{
"webhook_event": "webhook.test",
"webhook_timestamp": "2025-01-15T12:00:00.000Z",
"webhook_delivery_id": "e5f6a7b8-c9d0-1234-ef01-345678901234",
"webhook_data": {
"account_id": "your-account-id",
"model_identifier": "test",
"generation_status": "succeeded",
"generation_id": "00000000-0000-0000-0000-000000000000"
}
}
Handling events in code
Use the webhook_event field to route events to the correct handler:
import { type WebhookPayload } from 'babysea';
import { verifyWebhook } from 'babysea/webhooks';
async function handleWebhook(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get('x-babysea-signature') ?? '';
const payload = await verifyWebhook(
rawBody,
signature,
process.env.BABYSEA_WEBHOOK_SECRET!,
);
switch (payload.webhook_event) {
case 'generation.completed':
await handleCompleted(payload);
break;
case 'generation.failed':
await handleFailed(payload);
break;
case 'generation.started':
await handleStarted(payload);
break;
case 'generation.canceled':
await handleCanceled(payload);
break;
case 'credits.low_balance':
await handleLowBalance(payload);
break;
case 'webhook.test':
// No action needed
break;
}
return new Response('OK', { status: 200 });
}
async function handleCompleted(payload: WebhookPayload) {
const { generation_id, generation_output_file } = payload.webhook_data;
// Download or store the output files
for (const url of generation_output_file ?? []) {
await saveOutput(generation_id, url);
}
}
async function handleFailed(payload: WebhookPayload) {
const { generation_id, generation_error, generation_error_code } = payload.webhook_data;
// Log the failure and notify the user
await logFailure(generation_id, generation_error, generation_error_code);
}
async function handleStarted(payload: WebhookPayload) {
const { generation_id } = payload.webhook_data;
// Update your application state
await updateStatus(generation_id, 'processing');
}
async function handleCanceled(payload: WebhookPayload) {
const { generation_id, credits_refunded } = payload.webhook_data;
// Handle cancellation
await updateStatus(generation_id, 'canceled');
if (credits_refunded) {
await notifyRefund(generation_id);
}
}
async function handleLowBalance(payload: WebhookPayload) {
if (payload.webhook_event !== 'credits.low_balance') return;
const { account_id, current_balance, thresholds_crossed } = payload.webhook_data;
await notifyFinanceTeam(account_id, current_balance, thresholds_crossed);
}