Webhooks API Reference
This page documents the Webhooks endpoints in the Core API.
Update Webhook Configuration
Updates webhook configuration for a specific channel.
URL: /v1/channels/{channelId}/webhook
Method: PUT
Auth required: Yes
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
webhook_url | string | Yes | URL where notifications will be sent |
webhook_secret | string | Yes | Secret key for webhook signature verification |
webhook_enabled | boolean | No | Whether webhook notifications are enabled (default: true) |
webhook_events | array | No | Array of event types to receive |
Example Request:
curl -X PUT "https://api.uos.example.com/v1/channels/channel-123/webhook" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-api.com/webhooks/orders",
"webhook_secret": "your-secret-key",
"webhook_enabled": true,
"webhook_events": ["order:created", "order:updated", "order:status_changed", "order:failed", "webhook:test"]
}'
Success Response:
- Code: 200 OK
- Content:
{
"id": "channel-123",
"name": "Amazon US Store",
"type": "amazon",
"webhook_url": "https://your-api.com/webhooks/orders",
"webhook_secret": "masked-for-security",
"webhook_enabled": true,
"webhook_events": ["order:created", "order:updated", "order:status_changed", "order:failed", "webhook:test"],
"webhook_last_sent": null,
"webhook_failures": 0,
"updated_at": "2023-06-01T12:00:00Z"
}
Error Responses:
- Code: 400 Bad Request
- Content:
{ "error": "Invalid webhook URL or events format" }
- Content:
- Code: 404 Not Found
- Content:
{ "error": "Channel not found" }
- Content:
Test Webhook Configuration
Sends a test webhook to verify the channel's webhook configuration.
URL: /v1/channels/{channelId}/webhook/test
Method: POST
Auth required: Yes
Example Request:
curl -X POST "https://api.uos.example.com/v1/channels/channel-123/webhook/test" \
-H "Authorization: Bearer YOUR_API_KEY"
Success Response:
- Code: 200 OK
- Content:
{
"message": "Test webhook sent successfully",
"webhook_id": "webhook-123456",
"delivery_status": "delivered",
"delivery_timestamp": "2023-06-01T12:05:00Z"
}
Error Responses:
- Code: 400 Bad Request
- Content:
{ "error": "Webhook not configured for this channel" }
- Content:
- Code: 404 Not Found
- Content:
{ "error": "Channel not found" }
- Content:
- Code: 500 Internal Server Error
- Content:
{ "error": "Failed to send test webhook", "details": "Connection timeout" }
- Content:
Configure OAuth 2.0 Authentication
Configure OAuth 2.0 client credentials flow for authenticating webhook deliveries to third-party endpoints.
URL: /v1/channels/{channelId}/oauth2
Method: PUT
Auth required: Yes
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
oauth2_enabled | boolean | Yes | Enable or disable OAuth 2.0 authentication |
oauth2_client_id | string | Yes* | OAuth 2.0 client identifier |
oauth2_client_secret | string | Yes* | OAuth 2.0 client secret |
oauth2_token_url | string | Yes* | OAuth 2.0 token endpoint URL |
oauth2_scope | string | No | OAuth 2.0 scope parameter for token requests |
oauth2_additional_params | object | No | Additional parameters for token requests |
*Required when oauth2_enabled is true
Example Request:
curl -X PUT "https://api.uos.example.com/v1/channels/channel-123/oauth2" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"oauth2_enabled": true,
"oauth2_client_id": "your-oauth-client-id",
"oauth2_client_secret": "your-oauth-client-secret",
"oauth2_token_url": "https://auth.provider.com/oauth/token",
"oauth2_scope": "webhook:write orders:read",
"oauth2_additional_params": {
"audience": "https://api.example.com"
}
}'
Success Response:
- Code: 200 OK
- Content: Returns the updated channel object with OAuth configuration (client secret will be masked)
Error Responses:
- Code: 400 Bad Request
- Content:
{ "error": "Missing required OAuth field: oauth2_client_id" }
- Content:
- Code: 404 Not Found
- Content:
{ "error": "Channel not found" }
- Content:
Get OAuth 2.0 Configuration
Retrieve the current OAuth 2.0 configuration for a channel.
URL: /v1/channels/{channelId}/oauth2
Method: GET
Auth required: Yes
Example Request:
curl -X GET "https://api.uos.example.com/v1/channels/channel-123/oauth2" \
-H "Authorization: Bearer YOUR_API_KEY"
Success Response:
- Code: 200 OK
- Content:
{
"oauth2_enabled": true,
"oauth2_client_id": "your-oauth-client-id",
"oauth2_client_secret": "masked-for-security",
"oauth2_token_url": "https://auth.provider.com/oauth/token",
"oauth2_scope": "webhook:write orders:read",
"oauth2_additional_params": {
"audience": "https://api.example.com"
},
"oauth2_token_cache": {
"expires_at": "2025-01-16T16:30:00.000Z"
}
}
Error Responses:
- Code: 404 Not Found
- Content:
{ "error": "Channel not found" }
- Content:
Test OAuth 2.0 Configuration
Test the OAuth 2.0 configuration by attempting to acquire an access token.
URL: /v1/channels/{channelId}/oauth2/test
Method: POST
Auth required: Yes
Example Request:
curl -X POST "https://api.uos.example.com/v1/channels/channel-123/oauth2/test" \
-H "Authorization: Bearer YOUR_API_KEY"
Success Response:
- Code: 200 OK
- Content:
{
"success": true,
"message": "OAuth 2.0 configuration is valid and working",
"token_acquired": true,
"oauth2_enabled": true,
"channel_id": "channel-123",
"tested_at": "2025-01-16T15:30:00.000Z"
}
Failure Response:
- Code: 200 OK (test endpoint always returns 200, check
successfield) - Content:
{
"success": false,
"message": "OAuth 2.0 configuration test failed: Invalid client credentials",
"token_acquired": false,
"oauth2_enabled": true,
"channel_id": "channel-123",
"error": "Invalid client credentials",
"tested_at": "2025-01-16T15:30:00.000Z"
}
Error Responses:
- Code: 404 Not Found
- Content:
{ "error": "Channel not found" }
- Content:
Remove OAuth 2.0 Configuration
Delete the OAuth 2.0 configuration for a channel, disabling OAuth authentication.
URL: /v1/channels/{channelId}/oauth2
Method: DELETE
Auth required: Yes
Example Request:
curl -X DELETE "https://api.uos.example.com/v1/channels/channel-123/oauth2" \
-H "Authorization: Bearer YOUR_API_KEY"
Success Response:
- Code: 200 OK
- Content:
{
"message": "OAuth 2.0 configuration deleted successfully",
"channel": {
"id": "channel-123",
"name": "Example Channel",
"oauth2_enabled": false,
"oauth2_client_id": null,
"oauth2_client_secret": null,
"oauth2_token_url": null,
"oauth2_scope": null,
"oauth2_additional_params": {},
"oauth2_token_cache": {}
}
}
Error Responses:
- Code: 404 Not Found
- Content:
{ "error": "Channel not found" }
- Content:
List Webhook Events
Returns a list of all supported webhook event types.
URL: /v1/webhook-events
Method: GET
Auth required: Yes
Example Request:
curl -X GET "https://api.uos.example.com/v1/webhook-events" \
-H "Authorization: Bearer YOUR_API_KEY"
Success Response:
- Code: 200 OK
- Content:
{
"events": [
{
"name": "order:created",
"description": "Triggered when a new order is created"
},
{
"name": "order:updated",
"description": "Triggered when an order is updated"
},
{
"name": "order:status_changed",
"description": "Triggered when an order's status changes"
},
{
"name": "order:failed",
"description": "Triggered when an order fails processing or validation"
},
{
"name": "webhook:test",
"description": "Used for testing webhook configuration"
}
]
}
View Webhook Delivery History
Returns the delivery history for a channel's webhooks.
URL: /v1/channels/{channelId}/webhook/history
Method: GET
Auth required: Yes
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Maximum number of records to return (default: 20, max: 100) |
offset | integer | No | Number of records to skip (for pagination) |
event_type | string | No | Filter by event type |
status | string | No | Filter by delivery status (delivered, failed) |
Example Request:
curl -X GET "https://api.uos.example.com/v1/channels/channel-123/webhook/history?limit=10&event_type=order:created" \
-H "Authorization: Bearer YOUR_API_KEY"
Success Response:
- Code: 200 OK
- Content:
{
"data": [
{
"id": "webhook-123456",
"event_type": "order:created",
"delivery_status": "delivered",
"delivery_timestamp": "2023-06-01T12:05:00Z",
"request_url": "https://your-api.com/webhooks/orders",
"request_headers": {
"X-Signature": "masked-for-security",
"Content-Type": "application/json"
},
"response_status": 200,
"response_body": "{\"received\":true}"
},
{
"id": "webhook-123457",
"event_type": "order:created",
"delivery_status": "failed",
"delivery_timestamp": "2023-06-01T11:05:00Z",
"request_url": "https://your-api.com/webhooks/orders",
"request_headers": {
"X-Signature": "masked-for-security",
"Content-Type": "application/json"
},
"response_status": 500,
"response_body": "{\"error\":\"Internal server error\"}",
"retry_count": 3,
"next_retry": "2023-06-01T11:20:00Z"
}
],
"meta": {
"total": 2,
"limit": 10,
"offset": 0
}
}
Error Responses:
- Code: 404 Not Found
- Content:
{ "error": "Channel not found" }
- Content:
Webhook Payload Format
Important: Webhook payloads include the complete order data, not just summary information. This includes the full items array with all product details including image URLs.
Event Envelope Structure
All webhook events use a standardized envelope structure with strict validation requirements:
{
"event_type": "string (required, minLength: 1)",
"event_id": "string (required, minLength: 1)",
"timestamp": "string (required, ISO 8601 date-time format)",
"data": "object (required)"
}
Validation Rules:
- No additional properties allowed at envelope level
- All envelope fields are required
timestampmust be valid ISO 8601 formatevent_typemust be one of the supported event types
Webhook Authentication Headers
When delivering webhooks, UOS includes authentication headers based on your channel configuration:
HMAC Signature (when webhook_secret is configured):
X-Webhook-Signature: sha256=abc123...
OAuth 2.0 Authentication (when OAuth 2.0 is enabled):
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Both Authentication Methods: When both HMAC and OAuth 2.0 are configured, UOS includes both headers for maximum security:
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-Webhook-Signature: sha256=abc123...
This dual authentication approach allows receiving systems to verify webhooks using either method or both methods for enhanced security.
Order Created Event
Example payload for order:created event:
{
"event_type": "order:created",
"event_id": "evt-123456",
"timestamp": "2023-06-01T12:00:00Z",
"data": {
"order_id": "order-123",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": "pending",
"created_at": "2023-06-01T12:00:00Z",
"updated_at": "2023-06-01T12:00:00Z",
"customer_info": {
"name": "John Smith",
"email": "john.smith@example.com",
"phone": "+1234567890"
},
"shipping_info": {
"method": "Home Delivery",
"address": {
"street": "123 Main St",
"city": "London",
"postal_code": "SW1A 1AA",
"country": "GB"
}
},
"payment_info": {
"method": "card",
"amount": 45.97,
"currency": "GBP",
"status": "pending"
},
"items": [
{
"item_id": "PROD123",
"sku": "PROD123-VAR1",
"name": "Premium Widget",
"quantity": 2,
"price": 19.99,
"status": "pending",
"image_url": "https://example.com/images/premium-widget.jpg",
"properties": {
"color": "blue",
"size": "medium"
},
"created_at": "2023-06-01T12:00:00Z",
"updated_at": "2023-06-01T12:00:00Z"
},
{
"item_id": "PROD456",
"sku": "PROD456",
"name": "Basic Gadget",
"quantity": 1,
"price": 5.99,
"status": "pending",
"image_url": null,
"properties": {},
"created_at": "2023-06-01T12:00:00Z",
"updated_at": "2023-06-01T12:00:00Z"
}
],
"place_info": {
"place_id": "place-456",
"place_external_id": "store-123",
"name": "Downtown Store",
"address": "456 High Street, London"
},
"totals": {
"subtotal": 45.97,
"tax": 0.00,
"shipping": 0.00,
"total": 45.97
}
}
}
Order Updated Event
Example payload for order:updated event:
{
"event_type": "order:updated",
"event_id": "evt-123457",
"timestamp": "2023-06-01T12:30:00Z",
"data": {
"order_id": "order-123",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": "processing",
"created_at": "2023-06-01T12:00:00Z",
"updated_at": "2023-06-01T12:30:00Z",
"customer_info": {
"name": "John Smith",
"email": "john.smith@example.com",
"phone": "+1234567890"
},
"shipping_info": {
"method": "Home Delivery",
"address": {
"street": "123 Main St",
"city": "London",
"postal_code": "SW1A 1AA",
"country": "GB"
}
},
"payment_info": {
"method": "card",
"amount": 45.97,
"currency": "GBP",
"status": "authorized"
},
"items": [
{
"item_id": "PROD123",
"sku": "PROD123-VAR1",
"name": "Premium Widget",
"quantity": 2,
"price": 19.99,
"status": "picked",
"image_url": "https://example.com/images/premium-widget.jpg",
"properties": {
"color": "blue",
"size": "medium"
},
"created_at": "2023-06-01T12:00:00Z",
"updated_at": "2023-06-01T12:30:00Z"
},
{
"item_id": "PROD456",
"sku": "PROD456",
"name": "Basic Gadget",
"quantity": 1,
"price": 5.99,
"status": "pending",
"image_url": null,
"properties": {},
"created_at": "2023-06-01T12:00:00Z",
"updated_at": "2023-06-01T12:00:00Z"
}
],
"place_info": {
"place_id": "place-456",
"place_external_id": "store-123",
"name": "Downtown Store",
"address": "456 High Street, London"
},
"totals": {
"subtotal": 45.97,
"tax": 0.00,
"shipping": 0.00,
"total": 45.97
},
"changes": {
"status": {
"from": "pending",
"to": "processing"
},
"items": [
{
"item_id": "PROD123",
"field": "status",
"from": "pending",
"to": "picked"
}
]
}
}
}
Order Status Changed Event
The order:status_changed event is triggered when an order's status changes. UOS supports various status transitions including the fulfillment workflow.
Supported Status Transitions
UOS supports the following key status transitions for the fulfillment workflow:
processing→picking(Order Picking Started)picking→picked(Order Picked)picked→retrieving(Order Being Retrieved)retrieving→collected(Order Collected)retrieving→shipped(Order Shipped)- Any status →
cancelled(Order Cancelled)
Note: processing status transitions are not sent via webhooks as this is an internal system status. Webhooks begin from the picking status onwards.
Order Picking Started (processing → picking)
Triggered when an order moves from processing to picking status:
{
"event_type": "order:status_changed",
"event_id": "evt-456789",
"timestamp": "2023-06-01T12:00:00Z",
"data": {
"order_id": "12e2a812-87a1-4174-9064-4059ac33f7d2",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": {
"from": "processing",
"to": "picking"
},
"metadata": {
"updated_by": "picker-app",
"user_id": "picker-042",
"user_name": "Sarah Johnson"
}
}
}
Order Picked (picking → picked)
Triggered when an order is picked. Includes detailed item picking information:
{
"event_type": "order:status_changed",
"event_id": "evt-456790",
"timestamp": "2023-06-01T12:10:00Z",
"data": {
"order_id": "12e2a812-87a1-4174-9064-4059ac33f7d2",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": {
"from": "picking",
"to": "picked"
},
"metadata": {
"updated_by": "picker-app",
"user_id": "picker-042",
"items": [
{
"item_id": "PROD123",
"quantity": 2,
"properties": {
"picked_quantity": 2,
"unpicked_quantity": 0,
"sub_item_id": null
}
},
{
"item_id": "PROD456",
"quantity": 1,
"properties": {
"picked_quantity": 0,
"unpicked_quantity": 1,
"sub_item_id": "PROD456-ALT"
}
}
]
}
}
}
Item Picking Properties:
quantity: Originally ordered quantity (always the customer's original order)picked_quantity: Actual quantity picked (≥ 0)unpicked_quantity: Quantity not picked (≥ 0)sub_item_id: Product ID of substitute item (if substitution occurred)
Understanding Picking Scenarios:
Fully Picked (No Substitution)
{ "item_id": "PROD123", "quantity": 2, "properties": { "picked_quantity": 2, "unpicked_quantity": 0, "sub_item_id": null } }The original item was fully picked.
Substitution Occurred
{ "item_id": "PROD456", "quantity": 1, "properties": { "picked_quantity": 1, "unpicked_quantity": 0, "sub_item_id": "PROD789" } }The original item (PROD456) was out of stock. A substitute item (PROD789) was picked instead. The
picked_quantityrefers to the quantity of the substitute item that was picked.Note: To get full details about the substitute item (name, price, barcode, etc.), you need to fetch the complete order using the GET order endpoint and look for the item with
item_id: "PROD789"in the order's items array.Out of Stock (No Substitution)
{ "item_id": "PROD456", "quantity": 1, "properties": { "picked_quantity": 0, "unpicked_quantity": 1, "sub_item_id": null } }Item was not available and no substitute was selected.
Short Pick (Partial Fulfillment)
{ "item_id": "PROD789", "quantity": 5, "properties": { "picked_quantity": 3, "unpicked_quantity": 2, "sub_item_id": null } }Only 3 out of 5 requested items were available and picked.
Partial Substitution
{ "item_id": "PROD101", "quantity": 5, "properties": { "picked_quantity": 3, "unpicked_quantity": 2, "sub_item_id": "PROD202" } }The original item was out of stock. A substitute was used, but only 3 units of the substitute were available (customer ordered 5).
Business Rules:
metadata.itemsarray is required and must have at least 1 item- When
sub_item_idis non-null,picked_quantityrefers to the substitute item quantity - Short/nil picks are indicated by
picked_quantity < quantity quantityalways represents the original order quantity and never changespicked_quantity + unpicked_quantityshould equalquantity
How to Get Substitute Item Details:
When a substitution occurs (sub_item_id is not null), the webhook only includes the substitute item's ID. To get the full details of the substitute item (name, price, barcode, etc.), you have two options:
- Fetch the complete order using
GET /v1/orders/{orderId}and find the item withitem_idmatching thesub_item_idvalue - Use the Drone API
/dts/drone/order/productsendpoint which includesallowed_substitutionswith full product details
Example: Processing a Substitution
// Webhook received indicates substitution
const webhookItem = {
"item_id": "PROD456",
"quantity": 1,
"properties": {
"picked_quantity": 1,
"unpicked_quantity": 0,
"sub_item_id": "PROD789"
}
};
// Fetch complete order to get substitute details
const order = await fetch(`/v1/orders/${orderId}`);
const substituteItem = order.items.find(item => item.item_id === "PROD789");
// Now you have full details:
console.log(substituteItem.name); // "Alternative Brand Product"
console.log(substituteItem.price); // 2.99
console.log(substituteItem.barcode); // "1234567890123"
Order Being Retrieved (picked → retrieving)
Triggered when an order moves to retrieving status (ready for pickup or delivery):
{
"event_type": "order:status_changed",
"event_id": "evt-456791",
"timestamp": "2023-06-01T14:00:00Z",
"data": {
"order_id": "12e2a812-87a1-4174-9064-4059ac33f7d2",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": {
"from": "picked",
"to": "retrieving"
},
"metadata": {
"updated_by": "fulfillment-system",
"user_id": "system"
}
}
}
Order Collected (retrieving → collected)
Triggered when a Click & Collect order is collected by the customer:
{
"event_type": "order:status_changed",
"event_id": "evt-456792",
"timestamp": "2023-06-01T15:33:12Z",
"data": {
"order_id": "12e2a812-87a1-4174-9064-4059ac33f7d2",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": {
"from": "retrieving",
"to": "collected"
},
"metadata": {
"updated_by": "picker-app",
"user_id": "picker-042",
"user_name": "Sarah Johnson",
"collected_by": "John Smith",
"notes": "ID verified at pickup",
"rejected_items": [
{
"item_id": "PROD456",
"quantity": 1,
"reason": "collection_rejected_by_customer"
}
]
}
}
}
Collection Metadata:
collected_by: Name of person who collected the ordernotes: Additional collection notesrejected_items: Array of items rejected during collection (optional)
Rejection Reasons:
collection_rejected_by_customer: Customer refused the itemcollection_rejected_by_store: Store couldn't provide the item
Generic Status Change Example
For other status transitions, the basic format applies:
{
"event_type": "order:status_changed",
"event_id": "evt-456789",
"timestamp": "2023-06-01T12:10:00Z",
"data": {
"order_id": "12e2a812-87a1-4174-9064-4059ac33f7d2",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": {
"from": "processing",
"to": "shipped"
},
"metadata": {
"updated_by": "fulfillment-system",
"user_id": "system",
"tracking_number": "TRK123456789"
}
}
}
Cancelled Status Webhook
When an order status changes to cancelled, additional cancellation metadata is included:
{
"event_type": "order:status_changed",
"event_id": "evt-123459",
"timestamp": "2023-06-01T12:35:00Z",
"data": {
"order_id": "order-123",
"channel_id": "channel-123",
"order_number": "ORD-001",
"status": {
"from": "processing",
"to": "cancelled"
},
"metadata": {
"updated_by": "system",
"cancellation_reason": "customer_request",
"cancellation_category": "customer",
"notes": "Customer requested cancellation via support ticket #12345",
"user_id": "user-123",
"user_name": "John Smith"
}
}
}
Cancellation Metadata Fields:
cancellation_reason(string): Standardized cancellation reason code (e.g.,customer_request,picking_rejected_by_store)cancellation_category(string): Cancellation category (e.g.,customer,store,system)notes(string): Human-readable description or notes about the cancellation. For system-generated cancellations (e.g., items unavailable during picking), this contains a descriptive explanation. For manual cancellations, this contains user-provided notes.user_id(string, optional): ID of the user who cancelled the orderuser_name(string, optional): Name of the user who cancelled the order
Example: Store Rejection During Picking
When an order is cancelled due to items being unavailable during the picking process:
{
"event_type": "order:status_changed",
"event_id": "evt-890456",
"timestamp": "2023-06-01T14:22:00Z",
"data": {
"order_id": "order-456",
"channel_id": "channel-123",
"order_number": "ORD-456",
"status": {
"from": "picking",
"to": "cancelled"
},
"metadata": {
"updated_by": "drone_api",
"cancellation_reason": "picking_rejected_by_store",
"cancellation_category": "store",
"notes": "All items unavailable during picking - rejected by store"
}
}
}
Order Failed Event
The order:failed event is triggered when an order operation fails. It uses an operation-type oriented approach that provides specific information about what operation failed.
Base Structure
All order:failed events follow this structure:
{
"event_type": "order:failed",
"event_id": "evt-{operation}-{unique_id}",
"timestamp": "2023-06-01T12:15:00Z",
"data": {
"channel_id": "channel-123",
"order_number": "ORD-002",
"operation_type": "order_status_update",
"error_count": 1,
"metadata": {
"errors": ["Array of error messages"],
"error_code": "SPECIFIC_ERROR_CODE",
// Operation-specific fields...
}
}
}
Operation Types
The webhook uses operation_type as the primary identifier for what operation failed:
order_create- Order creation failedorder_status_update- Order status update failedorder_validation- Order validation failedplace_resolution- Place ID resolution failed
Operation-Specific Metadata
Each operation type includes specific metadata fields in addition to the base errors array:
order_status_update
Used when an order status update operation fails:
{
"operation_type": "order_status_update",
"metadata": {
"errors": ["Invalid status transition from pending to shipped"],
"error_code": "INVALID_STATUS_TRANSITION",
"requested_status": "shipped",
"current_status": "pending",
"valid_transitions": ["processing", "cancelled"],
"processor": "order-service",
"updated_by": "api-user",
"failure_type": "processing"
}
}
Key Fields:
requested_status- The status that was requestedcurrent_status- The current status of the ordervalid_transitions- Array of valid status transitions from current statusprocessor- Which component failedupdated_by- Who/what requested the status change
order_validation
Used when order data fails validation:
{
"operation_type": "order_validation",
"metadata": {
"validation_errors": [
"Missing required field: customer_info.name",
"Invalid shipping_info.method: must be 'Home Delivery' or 'Click & Collect'"
],
"validator": "order-validator",
"order_data": {
"order_number": "ORD-101",
"has_items": true,
"channel_id": "channel-123"
}
}
}
Key Fields:
validation_errors- Array of validation error messages (required, minItems: 1)validator- Which validator detected the errorsorder_data- Relevant order data context
order_create
Used when order creation fails:
{
"operation_type": "order_create",
"metadata": {
"errors": ["Failed to create order in backend system"],
"retry_count": 3,
"order_data": {
"order_number": "ORD-003",
"channel_id": "channel-123"
}
}
}
Key Fields:
errors- Array of error messages (required, minItems: 1)retry_count- Number of retry attempts madeorder_data- Relevant order data context
place_resolution
Used when a place external ID cannot be resolved:
{
"operation_type": "place_resolution",
"metadata": {
"errors": ["Failed to resolve place for external_id: store-999"],
"place_external_id": "store-999"
}
}
Key Fields:
errors- Array of error messages (required, minItems: 1)place_external_id- The external place ID that couldn't be resolved
Complete Examples
Order Status Update Failure:
{
"event_type": "order:failed",
"event_id": "evt-ordsta-xyz789",
"timestamp": "2023-06-01T12:30:00Z",
"data": {
"channel_id": "channel-123",
"order_number": "ORD-456",
"operation_type": "order_status_update",
"error_count": 1,
"metadata": {
"errors": ["Cannot cancel order that is already shipped"],
"error_code": "INVALID_STATUS_TRANSITION",
"requested_status": "cancelled",
"current_status": "shipped",
"valid_transitions": [],
"processor": "order-service",
"updated_by": "api-user",
"failure_type": "processing"
}
}
}
Order Validation Failure:
{
"event_type": "order:failed",
"event_id": "evt-ordval-456def",
"timestamp": "2023-06-01T12:40:00Z",
"data": {
"channel_id": "channel-123",
"order_number": "ORD-101",
"operation_type": "order_validation",
"error_count": 2,
"metadata": {
"validation_errors": [
"Missing required field: customer_info.name",
"Invalid shipping_info.method: must be 'Home Delivery' or 'Click & Collect'"
],
"validator": "order-validator",
"order_data": {
"order_number": "ORD-101",
"has_items": true,
"channel_id": "channel-123",
"place_external_id": "4576"
}
}
}
}
Backward Compatibility
The failure_type field is still included for backward compatibility but is now optional. The primary field to use is operation_type.
Migration from Failure-Type to Operation-Type
Legacy Format (deprecated):
{
"failure_type": "validation",
"validation_errors": ["Error message"]
}
New Format (recommended):
{
"operation_type": "order_validation",
"validation_errors": ["Error message"],
"failure_type": "validation" // Still included for compatibility
}
Verifying Webhook Signatures
To ensure webhook payloads are sent by the Unified Order Service, verify the signature in each webhook request. The signature is included in the X-Signature header.
Example verification code (Node.js):
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler:
app.post('/webhooks/orders', (req, res) => {
const signature = req.headers['x-signature'];
const webhookSecret = 'your-webhook-secret';
if (verifyWebhookSignature(req.body, signature, webhookSecret)) {
// Signature is valid, process the webhook
console.log('Webhook received:', req.body);
res.status(200).json({ received: true });
} else {
// Invalid signature
console.error('Invalid webhook signature');
res.status(401).json({ error: 'Invalid signature' });
}
});
Test Event
Example payload for webhook:test event:
{
"event_type": "webhook:test",
"event_id": "evt-123459",
"timestamp": "2023-06-01T12:05:00Z",
"data": {
"message": "This is a test webhook event",
"channel_id": "channel-123"
}
}