Webhooks
Receive real-time HTTP notifications when events happen on a merchant's store. All deliveries are HMAC-SHA256 signed and include automatic retries.
Creating a webhook
Register a webhook endpoint using your app's access token:
curl -X POST https://api.bazex.co/webhooks \
-H "Authorization: Bearer fbat_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/bazex",
"events": ["order.created", "order.status_changed", "product.updated"],
"description": "Main webhook endpoint"
}'{
"id": "clx1webhook123",
"url": "https://your-app.com/webhooks/bazex",
"secret": "whsec_a1b2c3d4e5f6a7b8c9d0e1f2",
"events": ["order.created", "order.status_changed", "product.updated"],
"isActive": true,
"createdAt": "2026-02-20T10:00:00.000Z"
}Save the secret
secret is only returned once during creation. Store it securely — you'll need it to verify webhook signatures.Event catalog
You only receive events for scopes your app has been granted.
Orders
| Event | Description |
|---|---|
| order.created | New order placed |
| order.status_changed | Order status updated |
| order.delivered | Order marked as delivered |
| order.cancelled | Order cancelled |
Payments
| Event | Description |
|---|---|
| payment.completed | Payment successfully processed |
| payment.expired | Payment expired |
Products
| Event | Description |
|---|---|
| product.created | New product added |
| product.updated | Product details changed |
| product.deleted | Product removed |
Inventory
| Event | Description |
|---|---|
| product.out_of_stock | Product went out of stock |
| product.low_stock | Product stock below threshold |
| product.back_in_stock | Product restocked |
Reviews
| Event | Description |
|---|---|
| review.created | New review submitted |
| review.deleted | Review removed |
Lifecycle
| Event | Description |
|---|---|
| business.data_erasure | Merchant uninstalled — erase stored data |
Payload format
Every webhook delivery is a POST request with a JSON body:
{
"event": "order.created",
"timestamp": "2026-02-20T10:30:45.123Z",
"webhookId": "clx1webhook123",
"businessId": "clx1business456",
"data": {
"orderId": "clx1order789",
"orderNumber": "1042",
"amount": 1590,
"deliveryFee": 200,
"deliveryMethod": "DELIVERY",
"paymentMethod": "CARD",
"isGuest": false,
"createdAt": "2026-02-20T10:30:45.000Z"
}
}Delivery headers
| Header | Description |
|---|---|
| X-Webhook-Id | The webhook registration ID |
| X-Webhook-Event | Event type (e.g., order.created) |
| X-Webhook-Signature | HMAC-SHA256 signature (sha256=<hex>) |
| X-Webhook-Timestamp | Unix timestamp (seconds) when the delivery was sent |
| X-Bazex-Event-Id | Unique delivery ID — use for idempotency/deduplication |
| User-Agent | Bazex-Webhooks/1.0 |
Signature verification
Always verify the signature before processing a webhook. The signature is computed as:
signature = HMAC-SHA256(secret, "${timestamp}.${rawBody}")Using the @bazex/app-sdk:
import { verifySignature, isTimestampFresh } from '@bazex/app-sdk';
app.post('/webhooks/bazex', (req, res) => {
const signature = req.headers['x-webhook-signature'] as string;
const timestamp = parseInt(req.headers['x-webhook-timestamp'] as string);
const rawBody = JSON.stringify(req.body);
// Verify signature (constant-time comparison)
if (!verifySignature(signature, timestamp, rawBody, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Check timestamp freshness (default: 5 min window)
if (!isTimestampFresh(timestamp)) {
return res.status(401).send('Timestamp too old');
}
// Process the event
const { event, data } = req.body;
console.log(`Received ${event}`, data);
// Respond quickly — process async
res.status(200).send('OK');
});Or manually in any language:
const crypto = require('crypto');
function verify(signature, timestamp, body, secret) {
const payload = `${timestamp}.${body}`;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}Retry policy
If your endpoint returns a non-2xx status or times out (10s), Bazex retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 2 minutes |
| 3rd retry | 8 minutes |
| 4th retry | 32 minutes |
| 5th retry (final) | 2 hours |
Auto-disable
Idempotency
The X-Bazex-Event-Id header contains a unique delivery ID that stays the same across retries. Use it to deduplicate events:
app.post('/webhooks/bazex', async (req, res) => {
const eventId = req.headers['x-bazex-event-id'] as string;
// Check if already processed
if (await db.processedEvents.findUnique({ where: { id: eventId } })) {
return res.status(200).send('Already processed');
}
// Process event...
await db.processedEvents.create({ data: { id: eventId } });
res.status(200).send('OK');
});Testing webhooks
Send a test event to your webhook endpoint:
curl -X POST https://api.bazex.co/webhooks/WEBHOOK_ID/test \
-H "Authorization: Bearer fbat_..."This sends a webhook.test event with a test payload. The response includes the HTTP status code and duration of the delivery.
Data erasure obligation
When a merchant uninstalls your app, Bazex sends a business.data_erasure webhook 48 hours after the uninstall. Your app must delete all stored merchant data upon receiving this event.
{
"event": "business.data_erasure",
"timestamp": "2026-02-22T12:00:00.000Z",
"webhookId": "clx1webhook123",
"businessId": "clx1business456",
"data": {
"appId": "clx1app789",
"businessId": "clx1business456",
"uninstalledAt": "2026-02-20T12:00:00.000Z"
}
}Always delivered
- No scope is required — all apps with a webhook URL receive this event
- The 48-hour delay gives you time to handle any pending operations before cleanup
- Return a 2xx status to acknowledge — if not, retries follow the standard schedule
- Failing to comply with data erasure may result in app removal from the marketplace
Best practices
- Respond quickly — return 200 within a few seconds, process the event asynchronously
- Always verify signatures — never trust unverified payloads
- Use idempotency — store
X-Bazex-Event-Idto prevent duplicate processing - Use HTTPS — webhook URLs must use HTTPS in production
- Handle retries gracefully — your endpoint may receive the same event multiple times
- Handle data erasure — implement the
business.data_erasurehandler to delete merchant data on uninstall