Notifications Webhook

Certain events in the outlet (such as processing an order) can be listened to by a third party provider using a notifications webhook. This is a URI controlled by the provider that ROS can POST JSON to indicating that certain events have occurred.

The notifications webhook URI is specified during the onboarding process.

Top Level Notification

POST <ConfiguredWebhookUri>

Contains Headers

See custom headers defined at OutgoingRequestHeaders

X-ROS-Id: <Unique ID for the ROS server making the outgoing request>

X-ROS-NotificationId: <Unique ID for the individual notification that will be preserved across retry attempts>

X-ROS-AttemptNumber: <How many attempts have preceded this notification delivery (expect a string encoded number from 0-N)>

X-ROS-Signature: <see security below>

Body

A single JSON encoded WebhookNotificationBody as the POST body which will wrap the actual notification body (see the type property for more details)

IMPORTANT: The webhook notification will contain a field called "OrganisationCode" which is the "org_id" field you used when requesting your access token, and a field called "OutletID". These two fields paired together will always represent the unique outlet that the webhook notification applies to. DO NOT assume OutletID on its own is unique, as it is NOT. Outlet IDs may be reused across different organisations due to configuration templating. Always pair both the Organisation Code and OutletID together to uniquely identify an outlet globally.

Expected Response

HTTP 200 on success

Any other response code will trigger a retry. Retries will be staggered using an increasing backoff time. Expect approximately 10 retries over the course of an hour before the notification will be dropped.

Security

In order to validate that an incoming notification has originated from ROS, the signature header will include a signature signed using your configured WebhookSecret for that organisation.

The signature will be a (lowercase) hexadecimal HMAC signature of the webhook HTTP request body. The webhook secret will be the key and SHA256 will be the hash function.

Example signature: db143a3fc6c95ed5ba7d83ca03e9d3451a3798bdfd5fee4aa7b6bb3b195f552a

Common Pitfalls

Please ensure that the HMAC signature is being calculating from the raw HTTP response body (don't include the HTTP headers/request types). If using a HTTP client library you'll want to ensure you have access to the underlying response bytes/content and NOT a parsed intepretation.

The PowerEPOS C# HMAC generation code is similar to the following:

// this will form the raw bytes of the POST PowerEPOS sends. It's just the WebhookNotificationBody with the "body" property being set (in this case it's a MenuUpdatesWebhookBody but all the other types are identical)
byte[] bodyBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new WebhookNotificationBody<MenuUpdatesWebhookBody>
{
	Body = body,
	Type = WebhookNotificationType.MenuUpdate,
}))

// this will be your webhook secret converted to its raw UTF8 bytes
byte[] secretBytes = Encoding.UTF8.GetBytes("YOUR WEBHOOK SECRET")

// here's where we calculate the HMACSHA256 hash using the webhook secret as a key
// the hash will be an array of bytes
byte[] hashBytes;
using (HMACSHA256 hmac = new HMACSHA256(secretBytes))
{
	hashBytes = hmac.ComputeHash(bodyBytes);
}


// we convert the hash bytes into the hexadecimal representation - PowerEPOS use a method similar to https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727
string hexadecimalEncodingOfHashBytes = ByteArrayToHex(hashBytes);


// then when we build the outgoing HTTP request it looks something like this:
using (HttpRequestMessage notificationRequest = new HttpRequestMessage(HttpMethod.Post, "https://YOUR.WEBHOOK.URI"))
using (ByteArrayContent byteArrayContent = new ByteArrayContent(bodyBytes))
{
	byteArrayContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
	notificationRequest.Content = byteArrayContent;
	notificationRequest.Headers.Add("X-ROS-Signature", hexadecimalEncodingOfHashBytes);
	
	// there are other headers added but at this point the HTTP POST is fired to your webhook.
}

Notification Types

Processed Orders

This notification will be sent whenever the Outlet marks a remote order as processed (rejected/confirmed) AND that remote order has the ProviderExpectsProcessedNotification property set.

Depending on circumstances the notification may be for multiple remote orders across multiple outlets.

The WebhookNotificationBody will have the WebhookNotificationType set to ProcessedOrders and the body will be a ProcessedOrdersWebhookBody

This notification will be sent whenever an Outlet updates their menu, items or prices and is an indication that the provider should re-synchronise their menus

This notification may also be sent if ROS restarts or loses connectivity to POSCore in order to ensure that no menu update events were missed.

This notification will also be sent when an outlet first enables configuration with your provider.

The WebhookNotificationBody will have the WebhookNotificationType set to MenuUpdate and the body will be a MenuUpdatesWebhookBody

Outlet Disabled

This notification will be sent whenever an Outlet disables their configuration for your provider.

The provider should cease accepting/sending remote orders for this outlet when receiving this notification. Future attempts to send to this outlet will fail.

If the outlet re-enables their configuration, a Menu Update will be sent.

The WebhookNotificationBody will have the WebhookNotificationType set to OutletDisabled and the body will be a OutletDisabledWebhookBody

Order Prepared

This notification will be sent whenever an Outlet decides to notify that a particular remote order has been fully prepared and is ready for collection. It may also include information for the consumer about where the order can be collected from.

This notification is not mandatory and will be up to the individual outlets configuration whether they send it or not.

The WebhookNotificationBody will have the WebhookNotificationType set to OrderPrepared and the body will be a OrderPreparedWebhookBody

Product Stock Status

This notification will be sent whenever an Outlet detects that stock levels for a particular product have changed "significantly".

It's not designed to be able to allow fine grained tracking of individual stock levels but should provide enough information about a product that is running low on stock / out of stock before a user orders it.

The notification is intended to affect any instance of that productId across all menus for the given outletId

The WebhookNotificationBody will have the WebhookNotificationType set to ProductStockLevel and the body will be a ProductStockLevelWebhookBody

For more info please see the Stock Levels Endpoint Page

Realtime Stock Levels (POS)

This optional notification can be sent whenever an Outlet detects that stock levels for a particular product have changed at all.

It is designed to allow fine grained tracking of individual stock levels but notifications of this type are sent directly from the POS system to the provider's webhook.

The WebhookNotificationBody will have the WebhookNotificationType set to LiveProductStockLevels and the body will be a LiveProductStockLevelWebhookBody

For more info please see the Stock Levels Endpoint Page