Webhooks API
Webhooks allow your application to receive real-time HTTP notifications when events happen in your Mercozy store. Instead of polling the API, register a URL and Mercozy will POST event data to it automatically.
Quickstart
Get up and running in 3 steps:
Step 1: Register your endpoint
Get your API key from Settings > Integration > API Keys. Admin permission required. The webhook signing secret (whsec_xxx) is automatically generated when you create an endpoint and shown only once.
curl -X POST "https://api.mercozy.com/api/v1/external/webhooks" \
-H "X-API-Key: mk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "My ERP Integration",
"url": "https://your-app.com/webhooks/mercozy",
"events": ["order.created", "order.cancelled", "payment.received"]
}'Step 2: Handle incoming events
app.post('/webhooks/mercozy', (req, res) => {
// 1. Verify signature (see below)
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// 2. Process the event
const { type, data } = req.body;
switch (type) {
case 'order.created':
syncOrderToERP(data);
break;
case 'payment.received':
updateAccountingSystem(data);
break;
}
// 3. Respond quickly with 200
res.status(200).json({ received: true });
});Step 3: Test your endpoint
curl -X POST "https://api.mercozy.com/api/v1/external/webhooks/{id}/test" \
-H "X-API-Key: mk_live_your_key_here"Endpoints
/webhooks
List all registered webhook endpoints.
/webhooks
Register a new webhook endpoint.
/webhooks/:id
Update an existing webhook endpoint.
/webhooks/:id
Delete a webhook endpoint.
Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Unique webhook identifier (read-only) |
name | string | Yes | A descriptive name for this endpoint |
url | string | Yes | HTTPS endpoint URL to receive webhook events |
events | string[] | Yes | Array of event types to subscribe to |
secret | string | No | Signing secret for payload verification (read-only, generated on creation) |
isActive | boolean | No | Whether the webhook is active (default: true) |
createdAt | datetime | No | ISO 8601 timestamp (read-only) |
Available Event Types
Mercozy supports 18 webhook event types across 6 categories. Subscribe only to the events you need.
Orders
- order.created
Fired when a new order is placed
- order.updated
Fired when order details are modified
- order.cancelled
Fired when an order is cancelled
- order.completed
Fired when an order is marked as completed
- order.status_changed
Fired on any order status transition
Payments
- payment.received
Fired when payment is captured or COD is collected
- payment.refunded
Fired when a payment is refunded or cancelled
Products
- product.created
Fired when a new product is added
- product.updated
Fired when product details change
- product.deleted
Fired when a product is deleted
Inventory
- stock.low
Fired when stock falls below threshold
- stock.updated
Fired when stock quantity changes
Customers
- customer.created
Fired when a new customer is registered
- customer.updated
Fired when customer info is updated
Delivery
- delivery.batch_started
Fired when a delivery batch is dispatched
- delivery.batch_completed
Fired when all stops in a batch are done
- delivery.completed
Fired when an individual delivery is completed
- delivery.failed
Fired when a delivery attempt fails
Webhook Payload
Each webhook delivery includes the event type, timestamp, and the relevant resource data.
Order Event Payload
{
"id": "evt_clx1abc2def3",
"type": "order.created",
"timestamp": "2026-03-19T10:30:00Z",
"data": {
"id": "clx9order123",
"orderNumber": "ORD-1042",
"status": "PENDING",
"total": 4999,
"currency": "USD"
}
}Payment Event Payload
{
"id": "evt_clx2pay4ghi5",
"type": "payment.received",
"timestamp": "2026-03-19T10:35:00Z",
"data": {
"orderId": "clx9order123",
"orderNumber": "ORD-1042",
"amount": 4999,
"currency": "USD",
"paymentMethod": "CARD",
"status": "CAPTURED"
}
}Product Event Payload
{
"id": "evt_clx3prod6jkl",
"type": "product.created",
"timestamp": "2026-03-19T11:00:00Z",
"data": {
"id": "clx9prod789",
"name": "Organic Coffee Beans",
"sku": "COF-001",
"price": 1299,
"status": "ACTIVE"
}
}Delivery Event Payload
{
"id": "evt_clx4del7mno",
"type": "delivery.completed",
"timestamp": "2026-03-19T14:22:00Z",
"data": {
"orderId": "clx9order123",
"orderNumber": "ORD-1042"
}
}HTTP Headers
Every webhook request includes the following headers:
| Field | Type | Required | Description |
|---|---|---|---|
Content-Type | string | Yes | application/json |
X-Webhook-Signature | string | Yes | HMAC-SHA256 signature: sha256={hex_digest} |
X-Webhook-Timestamp | string | Yes | Unix timestamp in milliseconds |
X-Webhook-ID | string | Yes | Unique event ID for deduplication |
User-Agent | string | Yes | Mercozy-Webhook/1.0 (+https://www.mercozy.com/docs/webhooks) |
Signature Verification
Every webhook includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing the event to ensure the request originated from Mercozy.
Node.js
const crypto = require('crypto');
function verifyWebhook(rawBody, signature, timestamp, secret) {
// Signature is computed over "timestamp.body"
const message = timestamp + '.' + rawBody;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature'];
const ts = req.headers['x-webhook-timestamp'];
if (!verifyWebhook(req.body, sig, ts, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process event...
res.status(200).json({ received: true });
});Python
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Signature is computed over "timestamp.body"
message = (timestamp + '.').encode() + payload
expected = 'sha256=' + hmac.new(
secret.encode(),
message,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
sig = request.headers.get('X-Webhook-Signature')
ts = request.headers.get('X-Webhook-Timestamp')
if not verify_webhook(request.data, sig, ts, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
# Process event...
return {'received': True}, 200PHP
<?php
function verifyWebhook(string $payload, string $signature, string $timestamp, string $secret): bool {
// Signature is computed over "timestamp.body"
$message = $timestamp . '.' . $payload;
$expected = 'sha256=' . hash_hmac('sha256', $message, $secret);
return hash_equals($expected, $signature);
}
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
if (!verifyWebhook($payload, $signature, $timestamp, $webhookSecret)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// Process event...
http_response_code(200);
echo json_encode(['received' => true]);Retry Policy
If your endpoint returns a non-2xx status code or times out (10 seconds), Mercozy will retry delivery using exponential backoff:
| Field | Type | Required | Description |
|---|---|---|---|
Attempt 1 | No | Immediate | |
Attempt 2 | No | After ~1s | |
Attempt 3 | No | After ~4s |
After 3 failed attempts, the delivery is marked as failed. You can manually retry failed deliveries from the dashboard or via the API.
Best Practices
Respond within 5 seconds — Return a 200 status immediately and process the event asynchronously. Mercozy times out after 10 seconds.
Always verify signatures — Check the X-Webhook-Signature header before processing any event to prevent spoofed requests.
Handle duplicates (idempotency) — Use the X-Webhook-ID header to deduplicate events. Store processed event IDs and skip any you have already handled.
Use HTTPS endpoints — Mercozy requires HTTPS URLs in production to protect webhook data in transit.
Log and monitor — Log all incoming webhooks and monitor for failures. Use the delivery logs API to check delivery status.