Event Notifications

SnowBee can send real-time notifications to your Azure Service Bus queue or topic when important events occur, such as inventory changes or product updates. This allows your e-commerce platform to stay synchronized with SnowBee without polling the API.

Configuration

Event notifications are configured per eCom Store in the SnowBee application. To set up Azure Service Bus notifications, you'll need the following information:

Message Format

All event notifications are sent as JSON messages to your Azure Service Bus queue or topic. Each message contains a subject property that indicates the event type, and a message body with event details.

Message Structure

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z",
  "metadata": { "refundId": "019c9876-5432-7abc-8def-abcdef123456" }
}
FieldTypeDescription
entityIdstring or nullThe ID of the affected entity (product, SKU, order, etc.). May be null for schema-level events.
eventIdstringA unique UUIDv7 identifier for this event. Use for idempotency and deduplication.
datetimestringISO 8601 timestamp when the event occurred
metadataobject or nullOptional key-value map with event-specific context. Present on some event types (see individual events below).

The message subject property (accessible via ServiceBusReceivedMessage.Subject in C# or similar in other languages) indicates the event type.

Event Types

Products & Inventory

ProductUpdated

Triggered when a product is created or updated. This includes changes to product attributes, SKUs, images, or prices.

Recommended action: Call GET /products/:productId to fetch the updated product details.

Subject: ProductUpdated

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Product ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z"
}

InventoryUpdated

Triggered when inventory availability changes for a SKU. This can happen due to:

The event is only sent if the SKU is in your eCom store's assortment.

Recommended action: Call GET /skus/:skuId/inventory to fetch the updated inventory levels.

Subject: InventoryUpdated

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // SKU ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z"
}

CampaignUpdated

Triggered when a campaign is created or updated. This includes changes to campaign dates, pricing, or SKU assignments.

Recommended action: Call GET /campaigns to fetch the updated campaign list.

Subject: CampaignUpdated

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Campaign ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z"
}

Orders

OrderCreated

Triggered when a new order is created on your eCom store. This allows you to track orders as they enter SnowBee's fulfillment pipeline.

Recommended action: Call GET /orders/:orderIdOrNumber to fetch the order details.

Subject: OrderCreated

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Order ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z"
}

OrderReadyForPaymentCapture

Triggered when a capture is created for an order on an eCom store that has payment capture enabled. This indicates that the order (or part of it) has been picked and is awaiting payment capture before shipment can proceed. An order may have multiple captures (e.g., partial fulfillment), each with its own independent capture lifecycle.

Shipment of each capture is blocked until its payment capture is confirmed. Call GET /orders/:orderIdOrNumber to fetch the order with the captures array (which includes each capture's id, status, and lines), then call POST /orders/:orderId/captures/:captureId/payment_capture with status CONFIRMED or FAILED.

Required action: Call GET /orders/:orderIdOrNumber to get capture details and line items, capture payment externally, then call POST /orders/:orderId/captures/:captureId/payment_capture with the capture result.

Subject: OrderReadyForPaymentCapture

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Order ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z",
  "metadata": { "captureId": "019c9876-5432-7abc-8def-abcdef123456" }
}

OrderFulfilled

Triggered when a receipt is created for an order, indicating the order (or part of it) has been fulfilled and is ready for shipping.

Recommended action: Call GET /orders/:orderIdOrNumber to fetch the order with receipt details.

Subject: OrderFulfilled

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Order ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z"
}

OrderReadyForRefund

Triggered when a refund is created for an order. This indicates that a customer return has been processed in SnowBee and the e-commerce platform should issue a refund.

Call GET /orders/:orderIdOrNumber to fetch the order — the refunds array will contain the refund(s) with netAmount and grossAmount showing what should be refunded. After processing the refund externally, call POST /orders/:orderId/refunds/:refundId/confirm with status CONFIRMED to close the refund, or FAILED if the refund could not be processed.

Required action: Call GET /orders/:orderIdOrNumber to get refund details, process the refund via your payment provider, then call POST /orders/:orderId/refunds/:refundId/confirm with the result.

Subject: OrderReadyForRefund

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Order ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z",
  "metadata": { "refundId": "019c9876-5432-7abc-8def-abcdef123456" }
}

OrderShipped

Triggered when a shipment is created for an order, indicating the order (or part of it) has been shipped.

Recommended action: Call GET /orders/:orderIdOrNumber to fetch the order with shipment details.

Subject: OrderShipped

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Order ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z"
}

Stores

RetailStoreUpdated

Triggered when a retail store's name, store number, or visit address changes. This event is only sent for retail stores that belong to a company linked to your eCom store via click & collect.

Recommended action: Call GET /retail_stores to fetch the updated store details.

Subject: RetailStoreUpdated

{
  "entityId": "019c1234-5678-7abc-8def-123456789012",  // Retail Store ID
  "eventId": "019c5678-1234-7abc-8def-987654321098",
  "datetime": "2025-01-20T10:30:00.000Z"
}

Product Schema

These events are triggered when product schema data (lookup tables) are updated. The entityId will be null for these events since they affect the entire schema.

SubjectDescriptionAPI Endpoint
BrandsUpdatedA brand was added, modified, or removed/product_schema/brands
ProductCategoriesUpdatedA product category was added, modified, or removed/product_schema/product_categories
MainProductGroupsUpdatedA main product group was added, modified, or removed/product_schema/main_product_groups
SubProductGroupsUpdatedA sub product group was added, modified, or removed/product_schema/sub_product_groups
ProductConceptsUpdatedA product concept was added, modified, or removed/product_schema/product_concepts
ExclusivityLevelsUpdatedAn exclusivity level was added, modified, or removed/product_schema/exclusivity_levels
MainActivitiesUpdatedA main activity was added, modified, or removed/product_schema/main_activities
UnitOfMeasuresUpdatedA unit of measure was added, modified, or removed/product_schema/unit_of_measures
ProductColorsUpdatedA product color was added, modified, or removed/product_schema/product_colors
SizesUpdatedA size was added, modified, or removed/product_schema/sizes
DynamicAttributesUpdatedA dynamic attribute was added, modified, or removed/product_schema/dynamic_attributes
DynamicAttributeOptionsUpdatedA dynamic attribute option was added, modified, or removed/product_schema/dynamic_attributes_options

Best Practices

Idempotency

Each event includes a unique eventId (UUIDv7). Store processed event IDs to avoid reprocessing the same event if it's delivered multiple times (at-least-once delivery).

Event Ordering

Events may arrive out of order. Use the datetime field to determine the chronological order of events. For inventory updates, always fetch the latest state from the API rather than applying incremental changes.

Handling High Volume

During bulk imports or updates, you may receive many events in a short period. Consider:

Error Handling

If your message handler fails, the message will be returned to the queue for retry (depending on your Azure Service Bus configuration). Implement proper dead-letter queue handling for messages that consistently fail.

Example: C# Message Handler

using Azure.Messaging.ServiceBus;
using System.Text.Json;

public class SnowBeeEventHandler
{
    private readonly SnowBeeApiClient _apiClient;

    public async Task ProcessMessageAsync(ServiceBusReceivedMessage message)
    {
        var subject = message.Subject;
        var body = JsonSerializer.Deserialize<SnowBeeEvent>(message.Body);

        switch (subject)
        {
            case "InventoryUpdated":
                await HandleInventoryUpdated(body.EntityId);
                break;
            case "ProductUpdated":
                await HandleProductUpdated(body.EntityId);
                break;
            case "CampaignUpdated":
                await HandleCampaignUpdated();
                break;
            case "OrderCreated":
                await HandleOrderCreated(body.EntityId);
                break;
            case "OrderReadyForPaymentCapture":
                await HandleOrderReadyForPaymentCapture(body);
                break;
            case "OrderReadyForRefund":
                await HandleOrderReadyForRefund(body);
                break;
            case "OrderFulfilled":
                await HandleOrderFulfilled(body.EntityId);
                break;
            case "OrderShipped":
                await HandleOrderShipped(body.EntityId);
                break;
            case "RetailStoreUpdated":
                await HandleRetailStoreUpdated(body.EntityId);
                break;
            case "BrandsUpdated":
            case "ProductCategoriesUpdated":
            case "SizesUpdated":
                // ... other schema events
                await HandleSchemaUpdated(subject);
                break;
        }
    }

    private async Task HandleInventoryUpdated(string skuId)
    {
        var inventory = await _apiClient.GetSkuInventory(skuId);
        // Update your local inventory cache/database
    }

    private async Task HandleProductUpdated(string productId)
    {
        var product = await _apiClient.GetProduct(productId);
        // Update your local product catalog
    }

    private async Task HandleCampaignUpdated()
    {
        var campaigns = await _apiClient.GetCampaigns();
        // Update your local campaign/pricing data
    }

    private async Task HandleOrderCreated(string orderId)
    {
        var order = await _apiClient.GetOrder(orderId);
        // Track new order, update local order database
    }

    private async Task HandleOrderReadyForPaymentCapture(SnowBeeEvent body)
    {
        var orderId = body.EntityId;
        var captureId = body.Metadata?["captureId"];
        var order = await _apiClient.GetOrder(orderId);
        // Find the capture from the event metadata, or fall back to scanning for PENDING
        var capture = order.Captures.FirstOrDefault(c => c.Id == captureId)
            ?? order.Captures.FirstOrDefault(c => c.Status == "PENDING");

        if (capture != null)
        {
            // Calculate capture amount from capture lines and order line items
            // Capture payment via your payment provider
            // Then report capture result to SnowBee:
            // POST /orders/{orderId}/captures/{captureId}/payment_capture
            // { "status": "CONFIRMED", "capturedAmount": "1499.00" }
            // or { "status": "FAILED" } if capture failed
            await _apiClient.UpdatePaymentCapture(orderId, capture.Id, "CONFIRMED", capturedAmount);
        }
    }

    private async Task HandleOrderReadyForRefund(SnowBeeEvent body)
    {
        var orderId = body.EntityId;
        var refundId = body.Metadata?["refundId"];
        var order = await _apiClient.GetOrder(orderId);
        // Use the refundId from metadata to find the specific refund
        var refund = order.Refunds.FirstOrDefault(r => r.Id == refundId && r.Status == "OPEN");

        if (refund != null)
        {
            // Use the grossAmount on the refund for the refund amount
            var refundAmount = refund.GrossAmount;
            // Process refund via your payment provider
            // Then confirm the refund to SnowBee:
            // POST /orders/{orderId}/refunds/{refundId}/confirm
            // { "status": "CONFIRMED", "refundedAmount": "299.00" }
            await _apiClient.ConfirmRefund(orderId, refund.Id, "CONFIRMED", refundAmount);
        }
    }

    private async Task HandleOrderFulfilled(string orderId)
    {
        var order = await _apiClient.GetOrder(orderId);
        // Process fulfillment, update order status, trigger shipping
    }

    private async Task HandleOrderShipped(string orderId)
    {
        var order = await _apiClient.GetOrder(orderId);
        // Update order tracking, notify customer
    }

    private async Task HandleRetailStoreUpdated(string retailStoreId)
    {
        var stores = await _apiClient.GetRetailStores();
        // Update your local store data (name, number, address)
    }

    private async Task HandleSchemaUpdated(string schemaType)
    {
        // Refresh the relevant schema data
        // e.g., reload brands, categories, sizes, etc.
    }
}

public record SnowBeeEvent(
    string? EntityId,
    string EventId,
    string Datetime,
    Dictionary<string, string>? Metadata
);

Triggering Initial Sync

When setting up a new integration, you can trigger events for all entities in your eCom store assortment to populate your local data without polling.

Trigger Product Events

POST /v1/tenants/TENANT_ID/ecom_stores/ECOM_STORE_ID/products/trigger_events
Authorization: Bearer YOUR_ACCESS_TOKEN

Response:
{
  "eventsTriggered": 1250,
  "message": "Successfully triggered 1250 product events"
}

This triggers ProductUpdated events for all products in your assortment.

Trigger Inventory Events

POST /v1/tenants/TENANT_ID/ecom_stores/ECOM_STORE_ID/inventory/trigger_events
Authorization: Bearer YOUR_ACCESS_TOKEN

Response:
{
  "eventsTriggered": 3500,
  "message": "Successfully triggered 3500 inventory events"
}

This triggers InventoryUpdated events for all SKUs in your assortment, allowing you to sync current inventory levels.

Related Links