How It Works
Configure a webhook endpoint with a URL and a signing secret. When events occur, WhaleTools sends a POST request to your endpoint with the event payload signed with HMAC-SHA256.
- 1.Create a webhook endpoint via the API or dashboard.
- 2.Store the signing secret securely.
- 3.WhaleTools POSTs events to your URL as they happen.
- 4.Your server verifies the signature and processes the event.
Register a Webhook
Create an outbound webhook endpoint via the API. The response includes a signing_secret you'll use to verify payloads.
curl -X POST https://whale-gateway.fly.dev/v1/stores/{store_id}/webhooks \
-H "x-api-key: wk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/whaletools",
"events": ["order.created", "order.updated", "product.created"],
"description": "My app webhook"
}'
// Response
{
"object": "outbound_webhook_endpoint",
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://your-app.com/webhooks/whaletools",
"events": ["order.created", "order.updated", "product.created"],
"signing_secret": "a1b2c3d4e5f6...",
"is_active": true,
"total_delivered": 0,
"total_failed": 0
}Payload Format
POST https://your-app.com/webhooks/whaletools
Content-Type: application/json
X-Webhook-Signature: sha256=a1b2c3...
X-Webhook-Timestamp: 1741513200
X-Webhook-Event: order.created
X-Webhook-Delivery-Id: 7f3a9c2e-...
User-Agent: WhaleTools-Webhook/1.0
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "order.created",
"created_at": "2026-03-09T12:00:00.000Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"object": "order",
"order_number": "#10042",
"status": "completed",
"payment_method": "card"
}
}Signature Verification
Always verify the HMAC-SHA256 signature to ensure the webhook came from WhaleTools. The signature covers the raw request body and is compared using timing-safe equality.
import crypto from 'crypto';
function verifyWebhook(body: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
// Express handler
app.post('/webhooks/whaletools', (req, res) => {
const sig = req.headers['x-webhook-signature'] as string;
const raw = JSON.stringify(req.body);
if (!verifyWebhook(raw, sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Check timestamp freshness (< 5 minutes)
const age = Date.now() / 1000 - req.body.timestamp;
if (age > 300) {
return res.status(401).json({ error: 'Timestamp too old' });
}
// Process the event
console.log('Event:', req.body.event, req.body.data);
res.json({ received: true });
});Event Types
| Event | Description |
|---|---|
| order.created | A new order was placed. |
| order.updated | Order status, payment, or fulfillment changed. |
| order.paid | Payment confirmed for an order. |
| order.fulfilled | Order has been shipped or delivered. |
| order.cancelled | Order was cancelled. |
| product.created | New product added to catalog. |
| product.updated | Product details changed. |
| product.deleted | Product archived or deleted. |
| customer.created | New customer registered. |
| customer.updated | Customer profile changed. |
| inventory.adjusted | Stock level adjusted. |
| inventory.transferred | Stock transferred between locations. |
| checkout.completed | Checkout finalized into order. |
| agent.message | AI agent sent or received a message. |
Best Practices
- •Return 200 quickly. Process events asynchronously. WhaleTools considers non-2xx responses as failures and will retry.
- •Handle duplicates. Use the event ID for idempotency. The same event may be delivered more than once.
- •Verify signatures. Never trust unverified payloads. Always check the HMAC signature and timestamp.
- •Use HTTPS. Webhook endpoints must use TLS. HTTP URLs will be rejected.
- •Retry policy. Failed deliveries are retried up to 5 times with exponential backoff (30s, 2m, 10m, 1h, 6h). Check the delivery log via
GET /v1/stores/:storeId/webhooks/:id/deliveries. - •Test your endpoint. Use
POST /v1/stores/:storeId/webhooks/:id/testto send a test ping before going live.