Why verify?
Anyone on the internet canPOST to your webhook endpoint. Verifying the signature on each delivery proves the request came from BabySea and that the payload was not tampered with.
Using the SDK (recommended)
If you use the BabySea Node.js SDK, signature verification is handled for you:TypeScript
Signature format
BabySea includes aX-BabySea-Signature header on every delivery:
Terminal
| Part | Description |
|---|---|
t | Unix timestamp (seconds) when the signature was created |
v1 | HMAC-SHA256 hex digest of {t}.{rawBody} using your webhook secret |
Manual verification steps
1. Check timestamp freshness first
Reject deliveries wheret is more than 5 minutes old to prevent replay attacks. Doing this before the HMAC check lets you fail fast without performing crypto on stale requests:
TypeScript
2. Build the signed payload
Concatenate the timestamp, a literal., and the raw request body:
TypeScript
3. Compute the expected signature
HMAC-SHA256 the signed payload using your webhook secret:TypeScript
4. Compare using a constant-time function
Do not use=== for string comparison, it leaks timing information. Use a constant-time comparison to prevent timing attacks:
TypeScript
Convenience headers
In addition toX-BabySea-Signature, every delivery includes these headers that let you route or filter events without parsing the JSON body:
| Header | Value |
|---|---|
X-BabySea-Event | Event type, e.g. generation.completed |
X-BabySea-Delivery-Id | UUID matching webhook_delivery_id in the payload (use as an idempotency key) |
X-BabySea-Timestamp | ISO 8601 timestamp matching webhook_timestamp in the payload |
TypeScript
Your webhook secret
Your secret is displayed once when you register a webhook endpoint in your BabySea workspace. Store it as an environment variable, never commit it to source code. If you suspect your secret has been compromised, you can rotate the secret. BabySea will begin signing all new deliveries with the new secret immediately.Troubleshooting
1. Signature mismatch (Invalid signature)
The most common cause is body parsing. If your framework parses the JSON body before your handler runs, you’re verifying against a re-serialized string instead of the original bytes BabySea signed. Fix: Configure your route to receive the raw body as a string:- Express:
express.text({ type: 'application/json' })on the route. - Next.js App Router:
await req.text()instead ofawait req.json(). - Fastify: Use
rawBodywith thefastify-raw-bodyplugin.
2. Timestamp too old
Your server’s clock is more than 5 minutes out of sync with UTC, or the webhook was delayed in transit. Fix: Ensure your server uses NTP for time sync. If you need more tolerance, pass a customtoleranceSeconds value to verifyWebhook() (not recommended beyond 10 minutes).