Skip to main content

Delivery guarantee

BabySea delivers webhooks at least once. In rare cases (network issues, infrastructure failover) you may receive the same event more than once. Use the webhook_delivery_id field as an idempotency key to handle duplicates safely.

What triggers a retry

BabySea retries when your endpoint:
  • Returns a non-2xx HTTP status (including 3xx redirects, BabySea does not follow them)
  • Does not respond within 10 seconds.
  • Returns a connection error.

Retry schedule

BabySea makes up to 5 attempts using exponential backoff, all within approximately 81 seconds:
AttemptDelay after previous attempt
1Immediate
21 second
34 seconds
416 seconds
560 seconds
After 5 failed attempts, the delivery is marked as failed and no further retries are made. You can view delivery history and manually replay failed deliveries from your BabySea workspace.

Auto-disable

If a webhook endpoint accumulates 15 consecutive delivery failures, BabySea automatically disables it to avoid hammering an endpoint that is persistently down. You will see the endpoint marked as disabled in your workspace. Events that occur while a webhook is disabled are queued in BabySea’s dead-letter queue (DLQ) for up to 72 hours. After you re-enable the webhook, click Deliver queued events in your workspace to drain the DLQ. BabySea delivers each queued event sequentially with fresh delivery IDs, timestamps, and HMAC signatures.
DLQ events expire after 72 hours. Re-enable a disabled webhook and drain the queue promptly if you need to recover missed events.

Respond fast, process async

Your endpoint should acknowledge receipt immediately and process the payload in a background job. If your handler needs to do database writes or external API calls, do them after returning 200.
TypeScript
import express from 'express';
import { verifyWebhook } from 'babysea-sdk/webhooks';

// IMPORTANT: Use express.text() to get the raw body for signature verification.
// express.json() parses the body and may alter whitespace, breaking the signature.
app.post("/webhook", express.text({ type: 'application/json' }), async (req, res) => {
  const rawBody = req.body; // string, not parsed JSON
  const signature = req.headers['x-babysea-signature'] as string;

  // 1. Verify signature
  try {
    await verifyWebhook(rawBody, signature, process.env.BABYSEA_WEBHOOK_SECRET!);
  } catch {
    return res.status(400).send('Invalid signature');
  }

  // 2. Acknowledge immediately
  res.status(200).send('OK');

  // 3. Process in the background
  const payload = JSON.parse(rawBody);
  await queue.push(payload);
});

Delivery ordering

BabySea does not guarantee strict ordering of deliveries. For example, a generation.failed event may arrive before a retry of an earlier generation.completed event from a different generation. Always use webhook_data.generation_id to associate a delivery with the correct generation record.

Replaying deliveries

You can manually replay any previous delivery from your BabySea workspace. A replayed delivery gets a fresh webhook_delivery_id, webhook_timestamp, and HMAC signature, the original webhook_data is preserved. Replays are single-attempt (no automatic retries). Use this to recover from transient processing failures on your end.

Best practices

  • Use webhook_delivery_id for idempotency. Store processed delivery IDs and skip duplicates. BabySea guarantees at-least-once delivery, so the same event may arrive more than once.
  • Respond within 10 seconds. Offload slow work (database writes, downstream API calls) to a background queue after acknowledging.
  • Monitor your disabled webhooks. Set up alerts in your workspace so you notice when auto-disable triggers. DLQ events expire after 72 hours.
  • Verify signatures on every request. Never skip verification, even in staging. See Verify signature.