Webhooks
14. Webhooks
Webhooks send HTTP POST requests to your URL when events occur in the store. Use them for real-time sync with ERP, inventory, or analytics.
Setup (Admin)
- Go to Settings → API tab
- Scroll to Webhooks
- Click Add Webhook
- Enter your Delivery URL (must be HTTPS in production)
- Select Events to subscribe to
- Optionally set a Secret (auto-generated if omitted)
Sharing Webhook Credentials
After creating a webhook, provide the integration partner with:
- Webhook URL - Their endpoint URL (they configure this)
- Secret - For signature verification (shown only when creating/editing)
Available Events
| Event | Description |
|---|---|
order.created | New order created |
order.updated | Order updated |
order.cancelled | Order cancelled |
order.completed | Order completed |
product.created | New product created |
product.updated | Product updated |
product.deleted | Product deleted |
customer.created | New customer created |
customer.updated | Customer updated |
payment.completed | Payment completed |
payment.failed | Payment failed |
shipment.created | Shipment created |
shipment.updated | Shipment updated |
coupon.used | Coupon used |
review.created | Review created |
review.approved | Review approved |
* | All events (wildcard) |
How Webhooks Work
- Event occurs - e.g. order created, product updated
- System finds webhooks - All enabled webhooks subscribed to that event
- Delivery - HTTP POST to each webhook URL with the payload
- Headers -
Content-Type: application/json,X-Shopflow-Signature,X-Shopflow-Event - Logging - Delivery status (success/failed) and response code logged
Webhook Payload Format
Headers:
Content-Type: application/json
X-Shopflow-Signature: <HMAC-SHA256 hex signature>
X-Shopflow-Event: <event-type>Body: The raw request body is the event payload as JSON. The signature is computed from this exact string - use the raw body for verification, not a re-parsed/re-stringified version.
The payload structure is event-specific. Use the X-Shopflow-Event header to determine the event type.
Example - order.created / order.updated:
{
"id": 123,
"order_number": "ORD-1709468833000",
"customer_id": 42,
"status": "pending",
"payment_status": "pending",
"subtotal": "2400.00",
"shipping": "300.00",
"tax": "0.00",
"discount": "0.00",
"total": "2700.00",
"currency": "KES"
}Example - product.created / product.updated:
{
"id": 42,
"name": "Organic Flour",
"sku": "FLOUR-001",
"price": "12.99",
"stock": 100,
"status": "active",
"product_type": "simple"
}Example - product.deleted:
{
"id": 42
}Verifying Webhook Signatures
Always verify the X-Shopflow-Signature header to ensure the request came from your store.
Signature algorithm: HMAC-SHA256 of the raw request body with your webhook secret.
JavaScript (Node.js):
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(computed, 'hex'));
}
// Express middleware example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-shopflow-signature'];
const event = req.headers['x-shopflow-event'];
if (!verifyWebhookSignature(req.body.toString(), signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body.toString());
// Process payload...
res.status(200).send('OK');
});Python:
import hmac
import hashlib
import json
def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Shopflow-Signature', '')
signature = signature or request.headers.get('x-shopflow-signature', '')
if not verify_webhook_signature(request.data, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
payload = request.get_json()
# Process payload...
return 'OK', 200PHP:
<?php
function verifyWebhookSignature($rawBody, $signature, $secret) {
$computed = hash_hmac('sha256', $rawBody, $secret);
return hash_equals($computed, $signature);
}
// Usage
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SHOPFLOW_SIGNATURE'] ?? '';
if (!verifyWebhookSignature($rawBody, $signature, $webhookSecret)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true);
// Process payload...Webhook Best Practices
- Respond quickly - Return
200 OKwithin 2-3 seconds; process asynchronously if needed - Verify signature - Always validate the signature before processing
- Idempotency - Handle duplicate deliveries (same event may be retried)
- HTTPS - Use HTTPS for the webhook URL
- Logging - Log delivery attempts and failures for debugging
Webhook Delivery Logs
View delivery history in Settings → API → Webhooks → Actions → View Deliveries. Shows status, response code, and payload for each delivery.
Test Webhook
Use the Test action in the webhook list to send a test payload. This helps verify your endpoint is reachable and returns 200.
Webhook Management Endpoints
GET /webhooks- List webhooksPOST /webhooks- Create webhookGET /webhooks/:id- Get webhookPUT /webhooks/:id- Update webhookDELETE /webhooks/:id- Delete webhookGET /webhooks/:id/deliveries- Get webhook delivery logs