Webhooks
Webhooks
Webhooks enable your application to receive real-time notifications when events occur in The Neat Profit. This guide covers webhook setup, event types, and best practices.
Overview
Webhooks are HTTP POST callbacks sent to your server when specific events occur. They allow you to:
- React to inventory changes in real-time
- Get notified of variance alerts
- Track order status updates
- Monitor POS sync completions
Webhook Events
Inventory Events
inventory.updated
Sent when an inventory item’s quantity changes.
{
"event": "inventory.updated",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"item_id": "item_abc123",
"name": "Tito's Handmade Vodka",
"previous_quantity": 12,
"new_quantity": 10,
"change": -2,
"location": "main_bar",
"updated_by": "user_123"
}
}
inventory.low_stock
Sent when an item falls below its reorder point.
{
"event": "inventory.low_stock",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"item_id": "item_abc123",
"name": "Tito's Handmade Vodka",
"current_quantity": 2,
"reorder_point": 6,
"location": "main_bar",
"recommended_order": 12
}
}
inventory.count_completed
Sent when an inventory count is completed.
{
"event": "inventory.count_completed",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"count_id": "count_xyz789",
"location": "main_bar",
"items_counted": 156,
"variance_detected": 3,
"counted_by": "user_123"
}
}
Variance Events
variance.detected
Sent when variance exceeds the configured threshold.
{
"event": "variance.detected",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"item_id": "item_abc123",
"name": "Tito's Handmade Vodka",
"expected_consumption": 24,
"actual_consumption": 28.5,
"variance": 4.5,
"variance_percentage": 18.75,
"estimated_loss": 112.50,
"severity": "high",
"potential_causes": ["over-pouring", "theft"]
}
}
variance.threshold_exceeded
Sent when overall variance rate exceeds the threshold.
{
"event": "variance.threshold_exceeded",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"period": "2026-01-01 to 2026-01-15",
"variance_rate": 0.12,
"threshold": 0.08,
"total_loss": 2470.50,
"location": "main_bar"
}
}
Order Events
order.created
Sent when a new order is created.
{
"event": "order.created",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"order_id": "order_abc123",
"distributor_id": "dist_xyz789",
"distributor_name": "Southern Glazer's",
"status": "draft",
"total_amount": 1247.50,
"location": "main_bar"
}
}
order.submitted
Sent when an order is submitted to the distributor.
{
"event": "order.submitted",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"order_id": "order_abc123",
"distributor_name": "Southern Glazer's",
"confirmation_number": "ORD-2026-001234",
"estimated_delivery": "2026-01-20T00:00:00Z"
}
}
order.shipped
Sent when an order is shipped.
{
"event": "order.shipped",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"order_id": "order_abc123",
"tracking_number": "TRK123456789",
"carrier": "FedEx",
"estimated_delivery": "2026-01-20T00:00:00Z"
}
}
order.delivered
Sent when an order is delivered.
{
"event": "order.delivered",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"order_id": "order_abc123",
"delivered_at": "2026-01-20T14:30:00Z",
"received_by": "user_123",
"items_received": 12,
"items_damaged": 0
}
}
POS Events
pos.sync_completed
Sent when a POS sync completes.
{
"event": "pos.sync_completed",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"connection_id": "pos_conn_abc123",
"pos_type": "toast",
"sync_id": "sync_xyz789",
"sync_type": "full",
"duration_seconds": 270,
"sales_synced": 234,
"products_synced": 156,
"variance_detected": 3,
"status": "success"
}
}
pos.sync_failed
Sent when a POS sync fails.
{
"event": "pos.sync_failed",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"connection_id": "pos_conn_abc123",
"pos_type": "toast",
"sync_id": "sync_xyz789",
"error_code": "authentication_failed",
"error_message": "Invalid POS credentials",
"retry_after": 3600
}
}
Managing Webhooks
List Webhooks
Retrieve all configured webhooks.
GET /v1/webhooks
Response:
{
"data": [
{
"id": "webhook_abc123",
"url": "https://your-server.com/webhook",
"events": ["inventory.updated", "variance.detected"],
"status": "active",
"created_at": "2026-01-15T10:30:00Z"
}
]
}
Create Webhook
Create a new webhook subscription.
POST /v1/webhooks
Request Body:
{
"url": "https://your-server.com/webhook",
"events": ["inventory.updated", "variance.detected", "order.shipped"],
"secret": "your-webhook-secret"
}
Response:
{
"data": {
"id": "webhook_abc123",
"url": "https://your-server.com/webhook",
"events": ["inventory.updated", "variance.detected", "order.shipped"],
"secret": "whsec_abc123",
"status": "active",
"created_at": "2026-01-15T10:30:00Z"
}
}
Get Webhook
Retrieve details for a specific webhook.
GET /v1/webhooks/:id
Response:
{
"data": {
"id": "webhook_abc123",
"url": "https://your-server.com/webhook",
"events": ["inventory.updated", "variance.detected"],
"status": "active",
"delivery_stats": {
"total_delivered": 1523,
"total_failed": 2,
"last_delivery": "2026-01-15T10:30:00Z"
},
"created_at": "2026-01-10T08:00:00Z"
}
}
Update Webhook
Update an existing webhook.
PUT /v1/webhooks/:id
Request Body:
{
"events": ["inventory.updated", "variance.detected", "order.shipped"],
"status": "active"
}
Response:
{
"data": {
"id": "webhook_abc123",
"events": ["inventory.updated", "variance.detected", "order.shipped"],
"status": "active",
"updated_at": "2026-01-15T11:00:00Z"
}
}
Delete Webhook
Delete a webhook subscription.
DELETE /v1/webhooks/:id
Response:
{
"data": {
"id": "webhook_abc123",
"deleted": true
}
}
Signature Verification
Webhooks include a signature header for security:
X-Webhook-Signature: t=1234567890,v1=abc123...
Verifying Signatures
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const [timestamp, signatureValue] = signature.split(',');
const t = timestamp.split('=')[1];
const v1 = signatureValue.split('=')[1];
// Check timestamp (reject if older than 5 minutes)
if (Math.floor(Date.now() / 1000) - t > 300) {
return false;
}
// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(`${t}.${payload}`)
.digest('hex');
return v1 === expectedSignature;
}
Handling Webhooks
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
// Verify signature
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
// Handle the event
switch (event.event) {
case 'inventory.updated':
handleInventoryUpdate(event.data);
break;
case 'variance.detected':
handleVarianceAlert(event.data);
break;
case 'order.shipped':
handleOrderShipped(event.data);
break;
}
res.status(200).send('OK');
});
Best Practices
Respond Quickly
Acknowledge webhook deliveries within 5 seconds:
app.post('/webhook', (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
processWebhook(req.body);
});
Implement Retry Logic
Handle temporary failures gracefully:
async function processWebhook(event) {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
await handleEvent(event);
return;
} catch (error) {
attempt++;
if (attempt === maxRetries) {
logError('Failed to process webhook', error);
return;
}
await delay(1000 * attempt);
}
}
}
Use Idempotency
Design your handlers to be idempotent:
async function handleInventoryUpdate(data) {
const { item_id, new_quantity } = data;
// Use upsert to prevent duplicates
await db.inventory.update(
{ item_id },
{ quantity: new_quantity },
{ upsert: true }
);
}
Monitor Delivery
Track webhook delivery statistics:
const deliveryStats = {
total: 0,
success: 0,
failed: 0
};
app.post('/webhook', (req, res) => {
deliveryStats.total++;
try {
processWebhook(req.body);
deliveryStats.success++;
} catch (error) {
deliveryStats.failed++;
}
res.status(200).send('OK');
});
Troubleshooting
Webhook Not Received
- Check webhook status is
active - Verify URL is publicly accessible
- Check firewall allows incoming requests
- Review delivery stats in webhook details
Signature Verification Fails
- Ensure secret matches webhook configuration
- Check timestamp is within 5 minutes
- Verify payload is not modified
Delivery Failures
- Check server response time (must be < 5 seconds)
- Verify server returns 200 status
- Review error logs for specific failure reasons
Next Steps
- API Overview - Learn about API architecture
- Authentication - Secure your webhooks
- Integrations Guide - Webhook integration examples