Webhook API Documentation

Receive real-time order status updates via outgoing webhooks. Perfect for WhatsApp automation, CRM sync, and order tracking.

🔔
Events

Events are triggered automatically when order data changes. Each event sends a POST request to your configured endpoint.

Event Name Description Trigger
order.status_updated Fires when an order's shipping status changes Active
🔄
Status Transitions

Possible old_statusnew_status combinations you'll receive in webhooks.

From (old_status) To (new_status) Meaning
pendingpicked_upOrder picked up from warehouse
picked_uppreparingFulfiller is preparing the order
preparingpreparedOrder packed and ready to ship
preparedout_for_deliveryShipped, on the way to customer
out_for_deliverydeliveredSuccessfully delivered
out_for_deliveryreturnedCustomer refused / delivery failed
returneddeliveredRe-attempted and delivered
returnedreturn_receivedReturn received at warehouse
return_receivedreturned_to_stockProduct returned to inventory
🔑
Request Headers

Every webhook request includes these headers for verification and context.

X-Webhook-Signature
HMAC-SHA256 signature of the request body
X-Webhook-Event
Event name (e.g., order.status_updated)
X-Webhook-Timestamp
ISO 8601 timestamp of when the webhook was sent
Content-Type
application/json
📦
Payload Structure

Example payload for order.status_updated event.

{
  "event": "order.status_updated",
  "timestamp": "2026-05-09T12:00:00+02:00",
  "order": {
    "reference": "ORD-000129",
    "old_status": "out_for_delivery",
    "new_status": "delivered",
    "amount": 1799,
    "currency": "MAD",
    "tracking_number": "F-CSA6SFJ6EMY",
    "delivery_notes": "Call before delivery",
    "created_at": "2026-05-08T12:25:44+01:00"
  },
  "customer": {
    "name": "Karim Mansouri",
    "phone": "+212661234567",
    "city": "Casablanca",
    "address": "123 Bd Mohammed V"
  },
  "product": {
    "name": "Argivit Classic Tablets",
    "sku": "MA-ArgivitClassic"
  },
  "items": [
    {
      "product_name": "Argivit Classic Tablets",
      "sku": "MA-ArgivitClassic",
      "quantity": 1,
      "price": 1799
    }
  ]
}
🔐
HMAC Signature Verification

Verify the X-Webhook-Signature header to ensure the request is authentic and hasn't been tampered with.

const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// Usage in Express
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhook(
    JSON.stringify(req.body),
    signature,
    'your_secret_key'
  );
  if (!isValid) return res.status(401).send('Invalid signature');
  // Process the webhook...
});
import hmac, hashlib

def verify_webhook(body, signature, secret):
    expected = hmac.new(
        secret.encode('utf-8'),
        body.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Usage in Flask
@app.route('/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    if not verify_webhook(
        request.get_data(as_text=True),
        signature,
        'your_secret_key'
    ):
        return 'Invalid', 401
    # Process the webhook...
function verifyWebhook($body, $signature, $secret) {
    $expected = hash_hmac('sha256', $body, $secret);
    return hash_equals($expected, $signature);
}

// Usage
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];
$secret = 'your_secret_key';

if (!verifyWebhook($body, $signature, $secret)) {
    http_response_code(401);
    die('Invalid signature');
}
$data = json_decode($body, true);
// Process the webhook...
Best Practices